c c c
c 9 9 9 c c
c c
c c
| c c c c
cc
c c c cc
cc
c | cc c c c
cc
c ||cc
cc
c c |c
cc
| c c c
cc
c c|!ccc c
cc
c c| c c|c
c"c
c c c c c
c#c
c c !c c c
c$c
c c%c c c%c &| | c c|| c
c c c
c c|!ccc|| c
c c
c c !c c|| c
c c
c c !c cc|| c
c c
| c c |c'ccc c c|!cc c c c |c c| c
c c c
c c c|
c c
c c|c c| c
c c
c c c c c |c
c c
| c c c'c
#c
c c c(c)*c+,(,*c
c $c
c c+,(c,c(* -( c
c $c
c c(,*c.c( /)(,*c
cc
c c+,(,*c( /0 *c
cc
c c+,(,*c,)( (),*cc,c /+(( c
c c
c c+,(,*c)- *c
cc
| c c cc c c c
c
| c "c ||c cc
"c
" c c|!ccc c c|c
c"c
" c c1c
c#c
" c c!c
c#c
" c c|!cc c
c$c
" c c| |c c|!c | c
c$c
" "c c| |c| c|!c c
c c
c c | c #c ||c c !c |c
c
| c $cc ||c c |!c |c
"c
$ c c c(c,* (*c,c )c,+, 2(*c
c"c
$ c c/c,,,cc+-(/(c
c"c
$ c c* c |c
c#c
| c c ||c c |!c3
3 c
|,/(c c ||c c |!c c
c c
c c ||c c
cc
c c !c c|c c |c
cc
| c c'c
c
c c|!ccc'c
cc
c c | c
c"c
c c| c
c c
c c c
cc
c cc c cc'c c c
cc
c c| cc
cc
"c c cc
cc
| c c !c
"c
c c|!ccc !cc
c"c
c cc !cc cc
c#c
c ccc |c'c
c#c
c c !c c |
cc
c c c c
c c
c c c c
cc
"c c c c |c
cc
#c c !c c c
cc
$c c c
c#c
c c c | c |c'c
c" c
c c || c
c"c
| c c| c c|c c c cc c c !c c c| c c|c
"c c"c
c cc !ccc !c |cc |c c""c | c c c c
"#c
c c|!ccc c
c"#c
c c|!ccc c c
c"$c
c c c!c c c c
c#c
c c
c#c
c!c c c c
c c c!cc c c | c c c c c |cc |c
c#c #c
c c c c|!ccc c |c
c#c
c c |c'c |c c c |c
c#c
c4c| |c c c |
c#$c
c4c|!cc| |c c c
c#$c
c c| c c |c
c#$c
c c| c c c c |c
c$c
c c c c |c
c$ c
c c cc |c
c$c
c c |!c c c |c
c$c
"c c !c c|c c|c
c$c
c c c c c c c|c
c$$c
c c|c| c cc c
c
| c "c|c c
c c
" c c|!c| cccc'cc cc
c
" c c|!ccc c
c
" c c|c c
c
| c #cccc c | c
c $c
| c c | !c c| c ccc!c '5c
c c
c cc cc!c '5c
c
c c |c c| c c
c
c c c c cc c
c
| c cc c |c'c|c c| c
c c
c c c | c!c
c
c c|c c| c
#c
| c cc cc c c| c
c c
c c c cc c c| c
c
c cc!c c|!c5c
c
c c|c c c c c c
c
| c c c cc |c cc| |c
c c
c c|!ccc| c c| |c
"c
c c c cc cc |c
"c
c c| |c c c
c
c c c c
c
c c| c c
c
c c| c c c c
"c
"c c| cc c
$c
#c c c.c |c
c
| c cc c c|!ccc| cc
c c c
c c c ccc c |c c
c
c c |c ccc
"c
| c c|c ccc
c "c
c c ccc
#c
c c | c|!c |c
#c
c c1 c'c | c|!c c
$c
c c1c'c! c|!c c
c
c c c c|!c |c
c
c c cc c|!c3c
c
| c "c|c ccc
c "c
" c c cc c! c|!c 3c
"c
" c c| c c c c
#c
" c c c'c c| c |c'c |c
$c
" c c c cc cc c|!c |c
c
" c c c| |c ccc
c
| c #ccc !c
c "c
| c $cc |c cc'cc
c "c
| cc cc c c c c cc
c " c
| c c!c c| cc
c "c
| c ccc ccc c c c
c "c
c c c(c)*c,/)(,*c.c(c6c
"c
c c*()( c,)c,/)(c6c
""c
c c*()( c,)c,c-78 c ,c
"#c
c c8 -( c,)c+,8 )c,c(* c
"$c
c c*c,c(* c,c,)c,/)(c
#c
c c+2, 9(c,)c,/)(c
#c
c c
c
cc c c
c c
cc c c c c c
Ú Úc c
Los ordenadores son máquinas electrónicas capaces de hacer cálculos a gran velocidad y con gran precisión. Para resolver un problema usando una calculadora tenemos que tocar las teclas necesarias y anotar resultados intermedios hasta llegar al resultado final. Para volver a resolver el mismo problema, ya sea con los mismos valores de entrada o con otros, tendremos que volver a tocar todas las teclas de antes y volver a anotar resultados intermedios cuando haga falta, teniendo que entender en todo momento todos los pasos necesarios para dar con la solución del problema. Esta serie de pasos necesarios para resolver un problema es lo que se conoce con el nombre de ³Algoritmo´. En un ordenador más avanzado que una calculadora, como es el caso de un PC, podemos introducir una vez nada más los pasos necesarios para resolver el problema, el algoritmo, y en sucesivas ocasiones que haya que hacer el cálculo será suficiente con introducir los datos de entrada para que el ordenador resuelva el problema siguiendo las instrucciones del programa y nos devuelva como datos de salida la solución al problema. El usuario del programa no necesita conocer todos los pasos intermedios que han sido necesarios para encontrarla. El ordenador almacena todas las instrucciones y datos en memoria. Como la memoria del ordenador es un dispositivo electrónico digital, solo es capaz de trabajar con números y hacer operaciones muy simples como sumar, comparar si dos números son iguales o copiar
c c
datos a otras partes de la memoria o a los registros de datos del microprocesador. Un algoritmo o programa formado por estas instrucciones tan simples sería terriblemente largo y complicado, ya que cualquier proceso informático como dibujar una letra en la pantalla o calcular una raíz cuadrada necesitaría muchas de estas operaciones básicas y por lo tanto construir un programa directamente usando estas instrucciones sería bastante complicado. Para solucionar este inconveniente existen los intérpretes y los compiladores que son programas capaces de entender instrucciones más o menos fáciles y claras para nosotros y convertirlas en secuencias de instrucciones básicas en código máquina que son las únicas que entiende el ordenador. Para dar las instrucciones a los intérpretes o compiladores no podemos hacerlo de cualquier manera, sino que tenemos que seguir una serie de normas de sintaxis y usar determinadas palabras y símbolos. Esto es lo que se conoce como lenguaje de programación. Basic es un lenguaje de programación diseñado en la década de 1960 en los Estados Unidos originalmente para grandes ordenadores, pero que después se ha utilizado principalmente como medio de aprendizaje de la programación, más que para el desarrollo de aplicaciones comerciales. Las siglas B.A.S.I.C significan "Beginners all-purpose symbolic instruction code", es decir, lenguaje de programación simbólico multipropósito para principiantes. Hay muchas versiones, desde las primitivas que utilizaban números de líneas hasta las más modernas usadas en Microsoft Visual Basic que incluyen programación orientada a objetos para entornos Windows. QBasic es un Intérprete del lenguaje Basic que se incluye con los sistemas operativos MS-DOS a partir de las versiones 5.5 sustituyendo a otros más antiguos como GW-Basic. Se compone del editor de código, del intérprete de Basic y de un depurador sencillo todo integrado en el entorno de programación QBasic. La versión de Basic que soporta es bastante avanzada y no necesita que escribamos números de líneas, al mismo tiempo que cuenta con las instrucciones de control necesarias como para que podamos programar respetando los principios de la Programación Estructurada. El lenguaje de QBasic se ha quedado obsoleto por ser para el sistema MS-DOS y no para Windows, además de que al ser un intérprete no nos permite distribuir las aplicaciones que hagamos de forma independiente de QBasic. Aunque no nos vale para escribir programas comerciales, nos va a servir para aprender a programar de la forma más sencilla posible y sin complicarnos teniendo que escribir cosas que no entendemos todavía. Además el entorno está en castellano y es muy estable, por lo que nuestros programas nunca van a bloquear el ordenador. QBasic funciona perfectamente en Windows. Una vez terminado el curso todo lo aprendido lo podremos ampliar para empezar con Visual Basic, o bien nos servirá para empezar a
c c
programar en Pascal o en C, lenguajes mucho más completos que Basic, pero algo más complicados. Los fundamentos de programación que aprendamos durante el curso van a ser los mismos en la mayoría de lenguajes.
| c c c c *c *c *c *c
x x x x
x x x x
xccc cc cccccc cc c
ccc
x x xcc !!c"c !#$c Es tradicional en programación que al empezar con un nuevo lenguaje para empezar a entenderlo se escriba un programa que borre la pantalla y escriba las palabras "Hola mundo", allá vamos paso por paso. Desde MS-DOS entraríamos en el directorio donde tenemos QBasic y a continuación cargamos el programa. CD DOS[Enter] QBasic[Enter] O lo más común en estos tiempos, desde Windows buscamos el programa QBasic.exe o un acceso directo que apunte a él y hacemos doble clic sobre su icono. (Muchas veces encontraremos los accesos directos a QBasic con el nombre de Microsoft Quick Basic) En el editor podemos empezar a escribir todo el código de nuestro programa. Esto es como un procesador de textos, solo que en vez de escribir lo que queramos sólo se puede escribir en lenguaje Basic. Si escribimos otra cosa, cuando pasemos a la siguiente línea pulsando "Enter" el programa se dará cuanta de que algo no va bien y nos sacará un mensaje de error. Vamos a escribir nuestro programa. cls print "Hola Mundo" Ya está! Ahora que ya nos hemos hartado de escribir todo el código del programa vamos a probar a ver si funciona. Pulsamos la tecla F5 La pantalla se pondrá toda negra y en la esquina superior izquierda aparecerá de color gris el rótulo "Hola Mundo". Ya está. Nuestro programa hace lo que queríamos. Abajo aparecen unas letras que dicen "Presione cualquier tecla y continúe". Este rótulo lo escribe automáticamente QBasic para indicarnos que nuestro programa ha terminado y que cuando pulsemos cualquier tecla volveremos al editor de código. Pulsa cualquier tecla y ya ves otra vez la pantalla azul del editor con las instrucciones del programa.
c c
Para no tener que hartarnos otro día de escribir nuestro pedazo de programa vamos a grabarlo en el sistema de archivos de nuestro ordenador. Abrimos el menú "Archivo" y seleccionamos "Guardar". Aparece un cuadro de diálogo donde se nos pide el nombre del archivo. El manejo del cuadro de diálogo es muy sencillo, pero hay que tener en cuenta que como QBasic funciona en MS-DOS los nombres de los archivos no pueden tener más de 8 caracteres ni pueden incluir espacios en blanco. Tampoco es conveniente usar caracteres especiales ni letras acentuadas ni ñ. Los programas que hagamos en QBasic se guardan en un único fichero de texto ASCII con extensión ".BAS" que contiene el código fuente del programa, es decir, las instrucciones en lenguaje Basic que hemos escrito para que el programa funcione. x x c"%"&"&c'c (c)#c)$)c Ahora vamos a ver recordar el Código de nuestro programa. CLS PRINT "Hola Mundo" Vemos que hay dos instrucciones: CLS borra la pantalla (Igual que en MS-DOS) y PRINT "Hola Mundo" escribe en la pantalla "Hola Mundo". Las dos instrucciones se han ejecutado una detrás de otra, por eso podemos decir que un programa es un listado de instrucciones que se ejecutan de forma secuencial una detrás de otra desde la primera hasta la última. Al escribir el programa habrás observado que seguramente escribiste cls y al pasar de línea se puso en mayúsculas como CLS. Y después con print pasó lo mismo, pero "Hola Mundo" se ha quedado como lo escribiste. Esto es porque CLS y PRINT son instrucciones del lenguaje Basic que se ponen en mayúsculas automáticamente para dar mayor legibilidad al programa. "Hola Mundo" va entre comillas porque es un Literal o una expresión de cadena y se va a quedar siempre como tú lo has escrito. x x c*#%($%(!c +#$!,c Veamos en un momento como sería un programa en lenguaje PASCAL que haga lo mismo que el nuestro: Program Hola; uses crt; begin clrscr; writeln("Hola Mundo"); end. Y lo mismo en lenguaje C: #include < stdio.h > int main(void) { clrscr(); cprintf("Hola Mundo");
c c
return(0); } Podemos ver que en los dos están las instrucciones para borrar la pantalla (clrscr) y la de escribir (writeln en PASCAL y cprintf en C), pero ¿Por que llevan punto y coma al final? y ¿Qué son esos símbolos y esas otras palabras tan raras?... En otros lenguajes de programación hace falta escribir un "esqueleto básico" donde montar las instrucciones, en QBasic no. Nos limitamos a escribir lo que queremos que haga el programa y ya está. Estas estructuras básicas son necesarias en los otros lenguajes más avanzados, pero en QBasic como sólo vamos a aprender así lo tenemos más fácil sin tener que escribir cosas que todavía no entendemos. x x c#$ c$"%!#c Los programas que veamos al principio de este curso van a ser muy sencillos y se explican por sí solos, pero una vez vayamos aprendiendo a programar y hagamos programas cada vez más largos nos encontraremos con el problema de que pasado un tiempo tengamos que revisar el programa y no nos acordemos de lo que hacen algunas instrucciones, o que nuestro programa lo vea otra persona y no sepa por dónde cogerlo. Para solucionar este problema los lenguajes de programación nos dan la posibilidad de escribir comentarios dentro del listado del programa para explicar su funcionamiento. Lo que pongamos en los comentarios no altera para nada el funcionamiento del programa. En QBasic para escribir un comentario ponemos un apóstrofo ('). La tecla del apóstrofo está en el bloque alfanumérico del teclado en la fila de arriba, a la derecha del cero. Todo lo que escribamos a continuación del apóstrofo ya no será tenido en cuenta a la hora de ejecutar el programa. Veamos nuestro ejemplo con comentarios: R PROGRAMA HOLA MUNDO R Escribe en la pantalla "Hola Mundo" R R Este ejemplo forma parte del curso R de introducción a la programación R CLS RBorra la pantalla PRINT "Hola Mundo" REscribe "Hola mundo" Aquí hemos puesto una cabecera describiendo el programa y a continuación a la derecha de algunas instrucciones, en este caso de todas, hemos explicado para que sirven. En los programas ya terminados es conveniente poner la cabecera siempre y explicar las instrucciones más complicadas. El formato de la cabecera vendrá especificado en el proyecto, por ejemplo que contenga el nombre del programa, una descripción corta, el autor y la fecha. En nuestros programas no es necesario ser tan estrictos.
c c
Otra utilidad que se da a los comentarios en los entornos de programación es la de conseguir que un trozo de programa no se ejecute. Por ejemplo podemos poner como comentario la parte de un programa dónde se nos pide la clave de acceso y que ya hemos comprobado que funciona, para que no nos la pida cada vez que entremos a comprobar otra cosa. Al final nos tendremos que acordar de quitar estos comentarios para que todo funcione bien.
c | c c c *c *c *c *c *c
x x x x x
xcc-c c c. cc%cc cc"cc. cc& -cc. /cc$ 0c cc 1
x xcc"%&($$2"c!c'!#c3!! '#c Nuestro programa anterior funciona muy bien, pero siempre que lo ejecutemos va a suceder lo mismo, borrará la pantalla y escribirá un rótulo que diga "Hola Mundo". Vamos a escribir un programa un poco más complicado que nos pregunte nuestro nombre y nos salude. El resultado (lo que se ve en pantalla al ejecutarlo) podría ser el siguiente: Cómo te llamas? juanma Hola juanma El programa borrará la pantalla (Esto lo vamos a hacer siempre para no confundirlo con lo anterior que pueda haber), nos preguntará por nuestro nombre, nos dejará escribirlo con el teclado y cuando lo hagamos lo guardará en "algún sitio", después escribirá "Hola " seguido de nuestro nombre que lo habrá sacado del "sitio" donde lo guardó antes. Vamos a ver cómo será el código: CLS INPUT "Cómo te llamas? ", nombre$ PRINT "hola "; nombre$ Ahora tenemos una instrucción nueva, INPUT, que lleva dos parámetros separados por una coma. Esta instrucción lo que hace es dejar al usuario del programa que escriba algo con el teclado y cuando pulse la tecla Enter lo guarda en memoria. La primera parte "¿Cómo te llamas?" la escribe en pantalla tal como está para que el usuario sepa lo que se le pregunta. La segunda parte es una Variable, es decir el nombre que le hemos puesta a un "trozo" de la memoria del ordenador dónde vamos a guardar lo que ha escrito el usuario y que le hemos llamado nombre$ para después poder localizarlo. La siguiente instrucción, PRINT, es la misma que en el anterior programa. Lo que hace es escribir lo que lleva detrás. En esta ocasión va a escribir dos cosas, primero "Hola " con su espacio detrás tal como aparece dentro de las comillas, y a continuación todo seguido
"c c
escribirá el valor que haya guardado en la variable nombre$. No escribirá Hola nombre$ porque nombre$ no está entre comillas. Esto de las variables es una de las cosas más importantes de los lenguajes de programación. Los programas van a manejar datos que estarán grabados en la memoria del ordenador. Si no existieran las variables tendríamos que saber en que posición exacta los guardamos y después para recuperarlos habría que escribir los números de las direcciones de memoria y saber exactamente cuanto largo son los datos. Esto es muy complicado (Esto es lo que se hace en los Lenguajes Ensambladores), para hacerlo aquí más sencillo usaremos los nombres de variables y QBasic se encargará de ver en que lugar físico de la memoria mete la información y después de saber recuperarla cuando se la pidamos. x cc%#c&c&!%#c Nuestros programas van a poder trabajar con varios tipos de información, como son letras, números con o sin decimales, etc. Nuestro programa anterior usaba una variable de texto o de cadena de caracteres para almacenar nuestro nombre, pero en otros casos habrá que almacenar números más o menos grandes, por ejemplo. Veamos ahora los tipos de datos que hay en QBasic. 6"c7c +8c
" c
#(45c
&#$$2"c
#%" c
9c
%:cc c c
;c c<=
"% c
>c
"?c c
<=@c c <=
'" c
Ac
"?c cc c
x< @ =
c x< @ =@c
#" 'c
1c
"?cc c
0@:x;/c c 0@:x;/c
&( 'c
Bc
"?cc c c
0C:x;c c 0C:x;c
Vamos ahora a ver lo que significa esta tabla. En QBasic las variables van a ser de uno de estos cinco tipos. Si queremos almacenar palabras, nombres, fechas, horas, códigos postales, números de teléfono, DNIs, matrículas, etc. tendremos que usar una variable de tipo Cadena (STRING) cuyo nombre llevará al final el carácter $.
#c c
Para guardar números tendremos que usar alguno de los otros cuatro tipos de variables. Si nuestros números no van a llevar decimales usaremos una variable de tipo entero (INTEGER) que ocupa poca memoria (2 bytes) y además el ordenador trabaja con ellas muy rápido. Si los números enteros van a llegar a ser mayores de 32.767 tendremos que utilizar una variable de tipo Entero largo (LONG) en la que "caben" hasta números mayores que dos mil millones. Si vamos a usar números reales (que pueden llevar decimales) tendremos que usar variables de precisión sencilla (SINGLE). Para algún cálculo que necesite obtener resultados con muchos decimales usaremos las variables de doble precisión (DOUBLE) que son más lentas de manejar y ocupan más memoria que las anteriores. Si intentamos guardar en una variable un valor más grande (o más pequeño si es negativo) de lo que permite su tipo de datos, se producirá un "Error de Tiempo de Ejecución" y el programa se parará. x cc" #c&c3!! '#c Cada variable llevará un nombre que nosotros decidamos para usarla en nuestro programa. Tenemos que elegir nombres que no sean demasiado largos y que tengan algo que ver con lo que va a contener la variable. Los nombres de variables deben cumplir estas condiciones: *c Deben tener entre 1 y 40 caracteres. *c Pueden incluir letras y números. *c No pueden llevar la ñ ni letras acentuadas. *c El primer carácter tiene que ser una letra. Estos nombres de variable son válidos. *c Edad *c fechaNacimiento *c jugador1 *c iva16porCiento *c vidasRestantes Estos otros nombres no son válidos D cE"cF cc c Gcc
HcE$c cHGcc &$ 7$ '.#D cc ccccccccccEIc' Gcc *c -cE'. c Gcc
*c *c *c
Hay que tener en cuenta que QBasic no distingue de mayúsculas y minúsculas, por lo tanto una variable que se llame nivel será la misma que Nivel, NIVEL o niVEL. De hecho cuando cambiemos las mayúsculas y minúsculas de una variable se cambiarán todas las que ya haya escritas en el resto del programa, es decir, si hemos escrito en un sitio puntos y después escribimos PuntoS en otro sitio, la anterior también se cambiará a PuntoS.
$c c
Por convenio los nombres de las variables se escriben todo en minúsculas para diferenciarlos visualmente de las Palabras Claves que se van poniendo en mayúsculas automáticamente. Si una variable lleva dos palabras se suele escribir la primera letra de la segunda palabra en mayúsculas para separar un poco, por ejemplo nombreJugador totalActivo o nuevoRegistro. En las variables que deberían llevar la ñ hay varias formas: Para año se suele escribir cosas como ano, anno o anyo. De los acentos pasamos. En Visual Basic se pueden usar nombres de variables con ñ y acentos, pero en QBasic no. x cc&$'!!$2"c&c3!! '#c En otros lenguajes de programación (la mayoría) hay que Declarar las variables al principio del programa. Esto significa escribir una instrucción que diga algo así como "Voy a usar una variable que se va a llamar nombre y va a ser de tipo cadena" y así con cada una de las variables que vayamos a usar en el programa. En QBasic NO hace falta declarar las variables. La primera vez que escribimos el nombre de una variable QBasic reserva en memoria el espacio para utilizarla y ya está disponible durante el resto del programa, pero ¿Cómo sabe QBasic del tipo que queremos que sea la variable? En la tabla de tipos de datos había una columna que ponía Sufijo y que para cada tipo de datos tenía un símbolo distinto. " c
#(45c
#%" c
9c
"% c
>c
'" c
Ac
#" 'c
1c
&( 'c
Bc
Para indicar el tipo escribiremos uno de estos sufijos detrás del nombre de la variable, por ejemplo si nombre va a contener texto escribiremos nombre$ (Como hicimos en el ejemplo). A esto se llama declaración implícita, ya que dentro del nombre va incluído el tipo de datos. Ahora ¿Que pasa si escribimos en distintos sitios el mismo nombre de variable con distintos sufijos?, por ejemplo: variable$ = "Esto es un texto" variable% = 2000
c c
PRINT variable$ PRINT variable% Lo que ocurrirá es que QBasic entiende que son dos variables completamente distintas, cada una de un tipo y nos da como resultado: Esto es un texto 2000 Esto puede producir un Error de Lógica, es decir, el programa funciona perfectamente, pero a lo mejor no hace lo que esperábamos. Para evitar esto hay que poner a las variables nombres más descriptivos, y si en este caso de verdad queríamos dos variables es mejor ponerles nombres distintos para evitar confusiones. También puede pasar que se nos olvide ponerle a una variable el sufijo de tipo, entonces QBasic la tratará como de tipo real de precisión sencilla, que es el tipo predeterminado. Para ver como se cambia el tipo predeterminado mira en la ayuda de QBasic la instrucción DEFINT y similares. En la práctica a partir de ahora vamos a poner el sufijo $ a las variables de cadena y las demás las dejamos sin sufijo para que sean reales que admiten números muy grandes con o sin decimales. Si fuéramos a hacer programas muy depurados sería conveniente usar los otros tipos de variables cuando fuera necesario para que fueran más eficientes con el consumo de memoria. Las variables al ser usadas la primera vez antes de asignarles (guardar en ellas) ningún valor contienen un texto de longitud cero si son de cadena o el número 0 si son de otro tipo. En otros lenguajes de programación esto no es así y contienen cualquier valor que hubiera en esa posición de memoria de otros programas que la hubieran usado antes, por lo que se pueden producir errores. Aquí no. Lo último que queda es saber que el Tiempo de Vida de una variable es desde que se usa por primera vez (desde que se declara o se ejecuta la línea donde su nombre aparece por primera vez) hasta que el programa termina, después su valor se pierde. No podemos contar con que tengamos valores en memoria guardados de la vez anterior que ejecutamos el programa. x /cc$(&!&1c' c"c !#$1c No tener que declarar las variables puede parecer una ventaja, pero por el contrario #c("c !3c"$"3""% de QBasic. El problema menos importante es que usaremos nuevas variables cada vez que queramos sin acordarnos de las anteriores que a lo mejor ya no volvemos a usar, y por lo tanto nuestros programas van a usar más memoria de la necesaria. El problema más grave es que si en un sitio escribimos mal el nombre de una variable (o confundimos el sufijo de tipo) QBasic se creerá que
c c
estamos usando una nueva variable y nuestro programa seguirá funcionando como si nada, pero los cálculos no saldrán bien. Veamos el siguiente ejemplo, otra vez el programa saludador con un error de este tipo: cls input "Cómo te llamas? ", nombre$ print "hola "; nomber$ Una vez que lo ejecutemos podría pasar esto: Cómo te llamas? Juanma Hola No ha producido ningún error de Tiempo de Ejecución (No se ha parado y ha terminado correctamente), pero no nos ha saludado con nuestro nombre!!! Nuestro nombre lo guardó en la variable nombre$ cuando lo escribimos, pero al respondernos ha escrito "Hola " y a continuación el valor de la variable nomber$ que es otra variable nueva distinta y que está vacía. Aquí es muy sencillo de ver lo que pasa, pero en un programa de mil líneas con cuarenta variables puede ser muy complicado incluso saber cual variable es la que hemos escrito mal. Por esto hay que tener ESPECIAL CUIDADO en QBasic al escribir los nombres de las variables. En otros lenguajes las variables están declaradas al principio y si escribimos alguna mal nos da un error de compilación de tipo "Identificador desconocido" y en muchos casos hasta nos señala la línea donde está la variable mal escrita para que la corrijamos. En QBasic la mayoría de las veces que el programa funciona bien pero no hace lo que queremos es que hemos escrito mal el nombre de alguna variable. Cuando tenemos nombres de variables largos un truco para ver si están bien escritos todos es buscar una variable que esté bien escrita seguro y poner la última letra en mayúsculas, por ejemplo si tenemos identificador lo cambiaríamos por identificadoR que para QBasic sigue siendo lo mismo, pero al hacerlo se cambiarán todas las variables con nombre identificador por identificadoR en todo el programa y si al dar un repaso rápido vemos que alguna acaba con r minúscula es que alguna letra está mal escrita y habrá que repasarla y corregir el error.
c | c c|| c *c *c *c
x xcc-c c c x cc&J-cc x cc -cc c
x xcc"%&($$2"c!c'!#c$"#%!"%#c Cuando hagamos programas que usen variables nos daremos cuenta de que hay variables cuyo valor realmente no cambia a lo largo de
c c
todo el programa. Podemos tratar estas variables de una forma especial para que el ordenador las maneje de forma más eficiente. Estas variables las definiremos como constantes al principio del programa dándoles un valor, y después podremos usar este valor dentro del programa cuando nos haga falta, pero no modificarlo.
x cc&4"$2"c&c$"#%!"%#c Usaremos la palabra clave CONST seguida del nombre de la constante, el signo igual y el valor que le vamos a dar. Ejemplos: CONST pi = 3.14 CONST iva = 16 CONST ruta = "c:\basic\prg\" Para los nombres valen las mismas reglas que para los nombres de variables, es decir, hasta 40 caracteres sin ñ ni acentos, etc... También se suelen poner todo en minúsculas. El tipo de datos lo adivina QBasic a partir del valor que le asignamos, por eso no es necesario poner los sufijos de identificación de tipo (%, !, &, #, $), pero si usamos muchas constantes sería recomendadle hacerlo para dar más legibilidad al programa. Al definir constantes numéricas podemos hacer referencias a otras constantes definidas anteriormente, por ejemplo: CONST precio = 380 CONST dobleprecio = precio * 2 CONST mitadprecio = precio / 2 Esto no se utiliza mucho, pero puede evitar errores. En el ejemplo si el valor de precio es correcto, seguro que también lo son los valores de las otras dos constantes. x cc$"&!$2"c&c(#!c$"#%!"%#c En los programas extensos puede ser recomendables usar constantes para determinados valores que alguna vez en el futuro puede que nos interese cambiar. Imaginemos un programa de contabilidad en el que se calculan precios con IVA del 16 por ciento. Lo más fácil sería escribir el número 16 en cada operación donde sea necesario y ya está, pero imaginemos que cambian las leyes y el IVA pasa a ser del 18 por ciento. Para seguir usando el programa habría que revisar todas las líneas del listado, con el enorme trabajo que esto puede suponer, y en todos los sitios relacionados con IVA dónde hubiera un 16 poner un 18, pero podrían pasar dos cosas: *c Que cambiáramos un 16 por un 18 en algo que no tuviera nada que ver con el IVA. *c Que en algún sitio dejemos el 16 para calcular el IVA. En cualquiera de los dos casos nuestro programa dejaría de funcionar correctamente ya que haría los cálculos mal.
c c
Para evitar este problema basta con definir al principio del programa una constante... CONST iva = 16 ... y dentro del programa usar el nombre de la constante en vez de escribir el número 16. Si cambian las leyes del IVA bastará con cambiar el valor de la constante... CONST iva = 18 ... y ya está, el programa seguirá funcionado perfectamente todo con el 18 por ciento. En los programas grandes bien estructurados se utilizan constantes para todos los valores que pueden cambiar alguna vez. Por ejemplo en un juego puede que en futuras versiones queramos cambiar la puntuación máxima o el número de vidas disponibles.
c | c c | cccc *c *c *c *c
x x x x
xcc-c cK# cc c'cc cc$cc D cc# ccc
x xcc"%&($$2"c!cK#c En el tema anterior hemos visto una forma de que nuestro programa se comunique con la memoria del ordenador: usando las variables. Aquí vamos a ver la forma de que nuestro programa se comunique con el usuario. La forma más sencilla de hacerlo es a través de la "Consola en Modo Texto", es decir leyendo lo que el usuario escriba con el teclado y dando los resultados a través de la pantalla en forma de texto (Como en MS-DOS). En otros temas más adelante veremos cómo dibujar en la pantalla, cómo usar la impresora y cómo leer y escribir información en el sistema de archivos. Antes de seguir hay que aclarar que "Leer" significa que el programa lea los datos de entrada que el usuario ha escrito, y "Escribir" significa poner en la pantalla los resultados de el programa para que el usuario los vea. No al revés, las instrucciones son para el ordenador. x cc"%!&!c&c&!%# c'c&#&c'c%$'!&c La orden que usaremos en QBasic para leer lo que el usuario escribe en el teclado es INPUT. Ya la hemos usado, pero vamos a verla con más tranquilidad. Su sintaxis más común sería: INPUT "Pregunta al usuario", variable *c "Pregunta al usuario" es una cadena de texto entre comillas que aparecerá en pantalla para que el usuario la vea y sepa lo que se le está preguntando. No puede ser una variable ni una expresión,
c c
solamente una cadena entre comillas. En otros lenguajes de programación no se da esta posibilidad y hay que escribir un rótulo justo antes usando otra instrucción. *c variable es el nombre de la variable dónde se almacenará la información que escriba el usuario. Al ejecutar esta instrucción se escribe en la pantalla el texto que hay entre comillas y a la derecha aparece el cursor intermitente para que el usuario escriba. El programa estará parado hasta que el usuario escriba lo que quiera y pulse la tecla "Enter". Entonces se almacena en la variable lo que ha escrito el usuario (El valor anterior que pudiera tener la variable se pierde) y el programa sigue con la siguiente instrucción que haya. El usuario podrá desplazarse con las flechas del teclado por el texto que está escribiendo para corregir algún error antes de pulsar "Enter" Para que lo que escriba el usuario no salga justo pegado al texto de la pregunta lo que se suele hacer es escribir un espacio en blanco al final dentro de las comillas. Si escribimos... INPUT "Cómo te llamas?",variable$ El resultado después de que el usuario termine de escribir sería Cómo te llamas?Juanma Pero si lo ponemos con el espacio al final... INPUT "Cómo te llamas? ",variable$ El resultado es parecido pero se ve mejor Cómo te llamas? Juanma La variable tiene que ser del tipo correcto, si le pedimos al usuario que escriba una palabra, la variable debe de ser de tipo cadena y si no tendrá que ser de tipo numérico con o sin decimales. Si solo ponemos la variable, sin pregunta al usuario, aparecerá un interrogante. Es decir, si ponemos... INPUT nombre_variable Aparecerá en la pantalla ?_ Para que no aparezca indicación alguna, solo el cursor, hay que poner las comillas vacías, por ejemplo... INPUT "", nombre_variable Esto puede ser útil cuando la pregunta que hacemos al usuario incluye algún cálculo o expresión de cadena. Como INPUT no permite evaluar expresiones en la pregunta al usuario, esta pregunta habrá que mostrarla antes en una instrucción PRINT normal de las que veremos más adelante con su punto y coma al final, y a continuación poner el input de esta forma para leer la información del usuario. x cc$"%'c&c#! %!5#c Con estas instrucciones puede pasar que el usuario escriba cosas no válidas, ya sea por descuido o para intentar provocar un fallo en el programa. Más adelante veremos como asegurarnos, por ejemplo de que si le pedimos el día del mes no pueda escribir 450, en este
c c
apartado solo vamos a ver lo que hace QBasic en situaciones que en otros lenguajes podrían dar un error. ¿Qué ocurre si el usuario pulsa la tecla Enter sin haber escrito nada? Si la variable es de texto se almacenará en ella una cadena de longitud cero, es decir "". Si la variable era de cualquier tipo numérico se almacenará en ella el número cero. ¿Qué ocurre si el usuario escribe letras cuando le pedimos un número? INPUT "Escribe un número: ",variable PRINT variable En otros lenguajes se produciría un error. QBasic saca un rótulo que pone "Empezar nuevamente" y nos vuelve a repetir la pregunta, hasta que no escribamos un número no seguimos. Escribe un número: No quiero Empezar nuevamente Escribe un número: Que no! Empezar nuevamente Escribe un número: 2 2 Si el usuario escribe un número cuando le pedimos letras se guarda el número como texto en la variable de tipo cadena, no podrá ser usado directamente para hacer operaciones matemáticas. ¿Qué ocurre si el usuario escribe un número muy grande? INPUT "Escribe un número: ",variable% PRINT variable% Si el valor escrito no cabe en la variable aparece un rótulo de "Desbordamiento" y se repite la pregunta hasta que el número quepa. Recordemos como ejemplo que en las variables de tipo entero el valor máximo es 32767: Escribe un número: 99999999999999999999999999999 Desbordamiento Empezar nuevamente Escribe un número: 32769 Desbordamiento Empezar nuevamente Escribe un número: 32000 32000 ¿Y si el usuario escribe algo que llega al extremo derecho de la pantalla? Se sigue escribiendo en la siguiente línea por la izquierda. QBasic no le dejará de escribir más de 256 caracteres. ¿Qué ocurre si el usuario escribe decimales cuando se le pide un número entero? INPUT "Escribe un número entero: ",variable% PRINT variable% QBasic redondeará el valor al guardarlo en la variable entera, si la parte decimal es menor que 0,5 se quitan los decimales, si es igual o
c c
mayor que 0,5 se aumenta una unidad, igual que hacíamos para convertir de Pesetas a Euros. Escribe un número entero: 4.4 4 Escribe un número entero: 4.499 4 Escribe un número entero: 4.5 5 Escribe un número entero: 4.59 5 Recordemos que en MS-DOS Normalmente se utiliza el punto como separador decimal. Si escribimos una coma QBasic se creerá que estamos escribiendo letras y nos repetirá la pregunta. Escribe un número: 6,2 Empezar nuevamente Escribe un número: 6.2 6 x cc#!'&! c#$ c"c!"%!''!c Como ya hemos dicho, la forma más básica de dar al usuario los resultados de nuestro programa es a través de la pantalla. En temas posteriores se explica cómo conseguir crear las pantallas de los programas para que cada cosa aparezca en un sitio determinado y con distintos colores, recuadros, etc, pero por ahora para aprender a programar nos limitaremos a escribir cosas una debajo de otra como en MS-DOS. Usaremos la pantalla de texto de forma que cuando lleguemos a escribir en la línea más baja de la pantalla todo subirá hacia arriba y desaparecerá lo que hubiera en la primera línea. Para aclarar la pantalla y no liarnos con lo anterior hemos puesto en todos los ejemplos CLS como primera instrucción para que se borre de la pantalla lo que hubiera de ejecuciones anteriores del programa actual o de otro, y se empiece a escribir en la parte superior. Ahora vamos a ver con más detalle la instrucción PRINT que es la que usamos parara escribir en la pantalla. Una aclaración. Si a alguien le parece que PRINT significa imprimir tiene razón, en informática a escribir en la pantalla del ordenador también se le puede llamar imprimir en pantalla. Además esta orden PRINT también se utiliza para imprimir como se verá más adelante. La sintaxis de la instrucción es: PRINT texto Donde texto puede ser una cadena de caracteres entre comillas, que se escribirá tal cual. PRINT "Esto es un texto" « o el nombre de una variable. mensaje$ = "Prueba superada" PRINT mensaje$
"c c
En este caso se escribirá el valor de que tenga la variable, en nuestro ejemplo se escribiría Prueba Superada. La variable también puede ser de tipo numérico... PRINT total% También podemos escribir el resultado de operaciones matemáticas... PRINT 2+2 En este caso se escribirá 4. Las operaciones matemáticas (Expresiones) las veremos con detalle en el tema siguiente. Después de escribir algo el cursor (invisible) pasa a la línea de abajo, por ejemplo PRINT "Uno" PRINT "Dos" PRINT "Tres" Escribiría... Uno Dos Tres Pero en algún caso nos puede interesar que no sea así y que se escriba lo siguiente a continuación en la misma línea. Para hacerlo no tenemos más que escribir un punto y coma al final de la instrucción sobre la que queremos seguir, por ejemplo: PRINT "Uno"; PRINT "Dos"; PRINT "Tres" PRINT "Cuatro" Escribiría... UnoDosTres Cuatro Normalmente en una instrucción PRINT se suelen escribir varias cosas, como vimos en el programa saludador que primero escribía la palabra Hola y después nuestro nombre que estaba almacenado en la variable nombre$. Para hacer esto basta separar con punto y coma (;) las distintas "cosas" que queremos escribir, por ejemplo: nombrePrograma = "Super Juego" nombreUsuario = "JM" PRINT "Hola "; nombreUsuario; ", bienvenido a "; nombrePrograma Escribiría... Hola JM, bienvenido a Super Juego Observa que los espacios entre palabras hay que ponerlos en algún sitio dentro de las comillas, de lo contrario saldría todo junto, incluso alguna vez es necesario hacer... PRINT unaVariable$ ; " "; otraVariable$ ...para que no salga todo junto. Ahora vamos a probar con esto: PRINT 2; 3; 4 Visto la anterior, el resultado tendría que ser... 234 Pero no, es
#c c
2 3 4 QBasic escribe siempre los números con un espacio delante y otro detrás. Lo que ha escrito exactamente es: Espacio 2 Espacio Espacio 3 Espacio Espacio 4 Espacio, el último espacio no lo vemos pero también lo ha escrito. Una consecuencia de esto será que los números por ahora nunca van a salir justo en la parte izquierda de la pantalla, sino una posición más adelante, por ejemplo... PRINT "Prueba" PRINT 25 PRINT 74 PRINT "Fin prueba" Escribiría... Prueba 25 74 Fin Prueba Pero de esto no nos tenemos que preocupar. Si intentamos arreglarlo vamos a complicar nuestros programas innecesariamente. Ya habrá tiempo de dibujar pantallas en los temas de ampliación. Una cosa que sí podemos hacer ya es usar lo que se llama "Posiciones de tabulación". Esto es que QBasic divide cada línea de la pantalla en posiciones que comienzan cada 14 caracteres, la primera en la posición 1, la segunda en la 14, la tercera en la 28, etc. Y si nosotros separamos las expresiones de la orden PRINT con comas en vez de puntos y comas los textos se escribirán en estas posiciones en vez de justo pegado a la anterior. Vamos con un ejemplo: PRINT "Uno", "Dos", "Tres" PRINT "Cuatro", "Cinco", "Seis" PRINT "Siete", "Ocho", "Nueve" Que escribiría en pantalla... Uno Dos Tres Cuatro Cinco Seis siete Ocho Nueve Esto será muy útil en los listados que veremos más adelante. Por supuesto en una misma instrucción podemos separar unas cosas con comas y otras con puntos y comas, según haga falta.
c | c c cc c *c *c *c *c *c *c
c
x x x x x x
/ / / / / /
xcc-c cc cc cc - cc$ cIc cc c L /cc c ccc - =cc c-
$c c
x / xcc-c cc c En este tema vamos a ver como construir las expresiones matemáticas y lógicas necesarias para que nuestros programas sea capaces de hacer cálculos. Una expresión sería lo equivalente a las fórmulas que escribimos en una hoja de cálculo (Excel), es decir una sucesión de números, operadores (signos más, menos, etc.) y nombres de variables, entre otras cosas, colocados en el orden correcto. x / cc cc -c Lo primero que vamos a ver en este tema es un operador que nos permita guardar "algo" en una variable. En el programa Saludador para guardar nuestro nombre en la variable nombre$ usábamos directamente la instrucción INPUT que se encargaba de leer los datos del teclado y guardarlos directamente en la variable. Aquí no empleábamos el operador de asignación, pero después hemos hecho cosas como: mensaje$ = "Prueba superada" o al declarar las constantes hacíamos: CONST iva = 16 En estos ejemplos se puede ver que hemos usado el signo Igual para "Asignar" a la variable que hay a su izquierda el valor de la "Expresión" que hay a su derecha, por lo tanto ya podemos decir que el signo igual es el operador de asignación en el lenguaje Basic. En el caso más sencillo: total = 500 hacemos que en la variable total se almacene el número 500, perdiéndose el valor que tuviera anteriormente. También podemos hacer: total = total + 100 En este caso estamos usando la variable total como un "acumulador" ya que vamos a almacenar en ella el valor que tenga antes de la asignación más cien. Dicho de otra forma, si la variable valía 500 ahora le vamos a almacenar su valor de 500 más 100, con lo que al final de la asignación pasará a valer 600. En el siguiente apartado se explican algunas cosas sobre estas asignaciones. Es importante tener claro que a una variable solo le podemos asignar los datos adecuados a su tipo, por ejemplo si a una variable de cadena le asignamos una expresión numérica o a una variable numérica le asignamos una cadena se producirá un Error de Tiempo de Ejecución y el programa se parará. También hay que tener en cuenta que si a una variable de tipo entero le asignamos una expresión cuyo valor es con decimales, el número almacenado se redondeará, por ejemplo... num% = 10 / 3 hará que num% valga 3 en vez de 3.3333, igual que num% = 20 / 3
c c
hará que num% valga 7 en vez de 6.6666. Esta vez se ha redondeado hacia arriba. Tiene que quedar claro que la variable "destino" siempre va a la izquierda del signo igual, y la expresión a la derecha. Las expresiones nunca van a ir solas. Siempre van en una asignación o en una estructura condicional que ya veremos más adelante. x / cc$ cIc c Estas dos palabras se usan mucho en programación para referirse a variables que van incrementando su valor a lo largo de la ejecución del programa. Normalmente serán de tipo numérico y no se tratan de ninguna forma en especial, solo que al asignarles un valor se hace de forma que el anterior no se pierda, por ejemplo: nivel = nivel + 1 total = total + subtotalLinea vidas = vidas - 1 tamaño = tamaño * 2 Es muy importante inicializarlas de forma correcta siguiendo estas normas: *c Si vamos a sumar o restar la inicializaremos a cero al principio del programa para que no empiecen con valores residuales. *c Si vamos a multiplicar las inicializamos a 1, porque si valen cero todo lo que multipliquemos por ella seguirá valiendo cero. La diferencia entre acumuladores y contadores es que los acumuladores se incrementan con cualquier número, como por ejemplo el total de una factura, mientras que los contadores se incrementan siempre con el mismo número, normalmente 1. x / cc c Lc Llamamos operadores aritméticos a los signos que usaremos para hacer operaciones aritméticas como sumas, restas, etc. !&c
" c
Mcc
# c
cc
c
Ncc
-c
Kcc
&.-c
&cc
cc.-cE-Gc
Occ
c
c c
El operador suma lo que hace es sumar valores. Se pueden encadenar tantas sumas como queramos, por ejemplo total = 2 + 74 + 7 + 25 + 82 El operador resta resta un valor a otro, por ejemplo: neto = peso - tara El operador asterisco hace multiplicaciones, por ejemplo: elDoble = n * 2 El operador división divide un valor entre otro, por ejemplo: kilos = gramos / 1000 El operador MOD nos devuelve el resto de una divisíón. Es especialmente útil para deducir si un número es par, ya que al dividir un par entre 2 el resto siempre es 0. Se usa entre los dos operandos igual que los símbolos anteriores, veremos su funcionamiento y su utilización en los temas siguientes. El operador "acento circunflejo" sirve para calcular potencias, por ejemplo 5^2 es cinco al cuadrado o también 5*5. También podríamos calcular potencias decimales como 4^2.5. Para escribir este símbolo pulsa mayúsculas y la tecla correspondiente dos veces, oirás un pitido y solo entonces aparecerá escrito en la pantalla. Si aparece dos veces borra uno. x / /cc c ccc -c Estos operadores hacen que una expresión devuelva un valor lógico, es decir, en vez de un número devolverá VERDADERO o FALSO. Esto nos será muy útil en las estructuras condicionales que veremos en los siguientes temas, dónde veremos muchos ejemplos de su utilización. !&c
" c
Pcc
c
Qcc
Icc
Rcc
cc
QPcc
Icc cc
RPcc
cc cc
RQcc
&cc
En QBasic el signo igual sirve tanto para asignaciones como para comparaciones, pero nunca se confunden ya que QBasic los evalúa de una u otra forma según dónde estén escritos.
c c
x / =cc c- c Estos operadores también devuelven un valor VERDADERO o FALSO a partir de los valores de las dos expresiones lógicas que unen. Igual que los anteriores se explicarán en los temas siguientes dedicados a estructuras condicionales. !&c
" c
!"&c
c7c
c
cc
"%c
c cc -c
8c
cc:.c
3c
cc. c
c
cc -c
Los tres primeros son las puertas lógicas elementales del álgebra de Boole, los otros se pueden construir a partir de las anteriores y por eso no suelen estar en otros lenguajes de programación y no hablaremos de ellos aquí. AND devuelve verdadero si las dos expresiones que une son verdaderas, en caso contrario devuelve falso. Por ejemplo: esNavidad = ((mes = 12) AND (dia = 25)) Podemos asegurar que es Navidad si el mes es 12 y el día es 25. Si el mes no es diciembre no será navidad aunque estemos a 25, tampoco si es un día de diciembre distinto de 25 y mucho menos si ni es diciembre ni es 25. Usamos por primera vez los paréntesis para dar más claridad, más adelante se explica cómo hacerlo. OR devuelve verdadero si alguna de las dos expresiones que une es verdadera, o las dos lo son. Si las dos son falsas devuelve falso, por ejemplo: puedesComprar = ((tuDinero > 0) OR (precio = 0)) En este caso la variable puedesComprar sería verdadero si tu dinero es mayor que cero (aprovechamos para dar un ejemplo del operador >) o si el precio es gratis, o las dos cosas. Solo sería falso si no tienes dinero y el producto a comprar vale dinero, con lo que las dos partes de la expresión serían falsas y el resultado también. NOT es un operador "monario". Esto significa que sólo tiene un operando, a diferencia de los otros que siempre están entre dos operandos. Lo que hace NOT es invertir el resultado de una expresión, es decir, si es verdadera devuelve falso y si es falsa devuelve verdadero.
c c
Para detallar los posibles valores que devuelven los operadores lógicos se construyen las llamadas "Tablas de la verdad" que representan todas las combinaciones posibles y los valores devueltos por cada operador.
%! '!c&c'!c3&!&c!!c!"&c 3&!&c
!"&c
3&!&c
Pc
3&!&c
3&!&c
!"&c
4!'#c
Pc
4!'#c
4!'#c
!"&c
3&!&c
Pc
4!'#c
4!'#c
!"&c
4!'#c
Pc
4!'#c
%! '!c&c'!c3&!&c!!cc 3&!&c
c
3&!&c
Pc
3&!&c
3&!&c
c
4!'#c
Pc
3&!&c
4!'#c
c
3&!&c
Pc
3&!&c
4!'#c
c
4!'#c
Pc
4!'#c
%! '!c&c'!c3&!&c!!c"%c "%c
3&!&c
Pc
4!'#c
"%c
4!'#c
Pc
3&!&c
Observa que la variable puedesComprar y la esNavidad del ejemplo anterior pueden ser de cualquier tipo numérico para poder ser verdaderas o falsas. QBasic entiende que una variable (o el resultado de una expresión) es falsa si vale 0 y verdadera en cualquier otro
c c
caso. Otros lenguajes tienen un tipo de datos específico para estas situaciones, pero aquí puede valer cualquier tipo de datos numérico. Es normal declarar constantes al principio de los programas para poder usar las palabras VERDADERO y FALSO en las expresiones y darle más claridad. Se haría: CONST FALSO = 0 pero para verdadero podemos hacer... CONST VERDADERO = NOT FALSO con lo que hacemos que VERDADERO sea justo lo contrario de FALSO. Muy lógico. c
| c c cc c c c c
El lenguaje BASIC incluye un montón de funciones que nos harán algunos cálculos sin necesidad de tener que programar nosotros todo lo necesario. Una función es una palabra que, insertada dentro de una expresión, llama a un pequeño "programita" que hace los cálculos y nos devuelve un resultado. Veamos este ejemplo: CLS INPUT "Escribe un número: ", num raiz = SQR(num) PRINT "Su raíz cuadrada es ";raiz Este programa nos pide que escribamos un número y lo guarda en la variable num. A continuación calcula la raíz cuadrada del número usando la función SQR y gualda el resultado en la variable raiz para al final escribirlo en pantalla. No hemos tenido que programar ni conocer las operaciones matemáticas necesarias para calcular la raíz cuadrada de un número, lo ha hecho automáticamente la función. Observa la forma de decirle a la función cual es el número que queremos que utilice para calcular la raíz cuadrada: Lo metemos entre paréntesis después del nombre de la función. Este número se dice que es un Parámetro que pasamos a la función. Si alguna función no necesita parámetros no ponemos nada, en otros lenguajes hay que poner los paréntesis vacíos detrás del nombre de la función, pero en QBasic no. El parámetro no tiene por que ser un número constante, puede ser a su vez otra expresión. En la instrucción PRINT podemos incluir cualquier expresión, por lo tanto en el ejemplo anterior nos podíamos haber ahorrado la variable raíz escribiendo directamente: CLS INPUT "Escribe un número: ", num PRINT "Su raíz cuadrada es "; SQR(num) Las funciones tienen tipo de datos como las variables, es decir, nos devuelven un resultado que puede ser cadena, entero, etc. y por lo tanto pueden llevar un sufijo de identificación de tipo a continuación del nombre, por ejemplo la función TIMER nos devuelve el número de
c c
segundos que han pasado desde las doce de la noche en formato numérico: segundos = TIMER mientras que la función TIME$ nos devuelve la hora actual en formato de cadena de texto con la hora, los minutos y los segundos separados por dos puntos: horaActual$ = TIME$ PRINT "Son las "; horaActual$ En este caso para guardar el valor devuelto por TIME$ hemos tenido que usar una variable de cadena. Ambas funciones no llevan parámetros porque si lo que hacemos es preguntar la hora no tenemos que decir nada más, ya la propia función verá como saca la hora del reloj interno del ordenador. Si una función lleva varios parámetros se pondrán separados por comas dentro de los paréntesis, por ejemplo: PRINT STRING$ (20,"Z") La función STRING$ devolverá una cadena con veinte zetas, es decir "ZZZZZZZZZZZZZZZZZZZZ". Para hacerlo necesita saber dos cosas: cuantas y cual letra tiene que repetir, por eso le damos dos parámetros separados por una coma. Observa también en el ejemplo que el primer parámetro debe de ser un número (o una expresión numérica) y el segundo tiene que ser una cadena (o una expresión de cadenas). Para saber de qué tipo es cada parámetro y el tipo del resultado que devuelve la función es muy importante consultar la ayuda de QBasic todas las veces que haga falta. Si intentamos pasar parámetros de otro tipo se producirá un error de tipos y el programa se parará. También se producirá un error de "Llamada a función no válida" si la función no es capaz de hacer los cálculos con los parámetros que le hemos pasado, aunque sean del tipo correcto. Por ejemplo, sabemos que no existe raíz cuadrada para los números menores que cero, por lo que se producirá un error de este tipo si hacemos PRINT SQR (-14) En caso de que pasemos como parámetro una expresión, por ejemplo PRINT SQR(miVariable) hay que tener mucho cuidado de que esta expresión no pueda llegar a valer menos que cero. Estas son algunas de las funciones más usadas. En temas posteriores irán apareciendo más. Para verlas todas y todos los tipos de datos que necesitan y devuelven mira la ayuda del QBasic. 4("$2"c
&#$$2"c
5'c
# EGc
SFc c
# Ex=Gc..cc
"%E Gc
c cc c c
"%Ex =Gc..cxc
c c
$"%E Gc
c c cE :c Pc<=
$"%Ex0=Gc..cc
$'" E Gc
c c c c
$'" Ex;;;;; Gc..c x;;;;;c
#"EGc
$ cc c
#"E;Gc..c; =<@c
$#EGc
$ cc c
$#E;Gc..c; <==;c
%!"EGc
$ c c c
%!"E;Gc..c ; @C;;Cc
'"E$ Gc
&..c c cc c c
'"ET"&!TGc..c/c
%E$ Gc
&..c c cc c c J c
%ET4 cccccTGc..c T4 Tc
($!#9E$ Gc
&..c c c c c I? c E"cJ c c cHccc c Gc
($!#9ET% c$ H TGc ..cT%!c $!#%!H!Tc
&9E$ 0c 0c Gc
&..c c c c Hc c c cc c -c c
&9ET -T0/0Gc ..cT Tc
#%" 9E 0c Gc
&..c c c J cc c?cc c c
#%" 9Ex;0TBTGc ..cTBBBBBBBBBBTc
%9c
&..c c c c c c
&..ccDc Tx=U/U;@Tc
&!%9c
&..c c J c c c c cJ c Hc
&..ccDcT;x ;c;xxTc
"V79c
&..c c cc c
&..S cT!Tcc c? c c cJc cT!c
"c c ? c c c c c c c D c c J- c
I? Tc
En una de las ampliaciones de este curso se habla de un conjunto de funciones muy útiles para el manejo de cadenas y en otro se explica en detalle la función INKEY$ que es especialmente útil para detectar teclas pulsadas en menús, juegos, preguntas que se responden con sí o no, etc.
| c c | | c cc *c *c *c *c *c *c *c
x x x x x x x
< < < < < < <
xcc-c cccc cc! . c c-c4 cc# S
cc! - /cc-c4cJ
=cc! . c c-c4%)"'#
c x < xcc"%&($$2"c!c'#c ' (#c&c$"%'c Hasta ahora al ejecutar nuestros programas se han ejecutado todas las instrucciones que aparecen en el listado del código fuente del programa, desde la primera hasta la última. A partir de ahora vamos a poder conseguir que una o varias instrucciones solo se lleguen a ejecutar si se cumple una condición, que si no se cumple se ejecuten otras, o incluso que algunas instrucciones se ejecuten más de una vez (Esto lo veremos en los siguientes temas). x < c!'%"!%3!c#' c"#%($$2"c4c Empecemos con un ejemplo. Un programa que nos pida la nota de un examen y nos felicite si está aprobado. CLS INPUT "Escribe la nota del examen: ", nota IF nota >= 5 THEN PRINT "Enhorabuena, has aprobado." END IF PRINT "Gracias por usar el programa." Ahora ejecutemos el programa. Hemos sacado un 7: Escribe la nota del examen: 7 Enhorabuena, has aprobado. Gracias por usar el programa. Volvamos a lanzar el programa, esta vez hemos sacado un 4: Escribe la nota del examen: 4 Gracias por usar el programa. Esta última vez se ha saltado una de las instrucciones del listado ¿Por qué?
#c c
Porque hemos usado la instrucción IF (si en inglés), una de las más importantes de todo lenguaje de programación estructurado, y le hemos puesto como condición que el valor almacenado en la variable nota sea mayor o igual que cinco con lo que hacemos que todas las instrucciones que haya entre IF y END IF solo se ejecuten si esta expresión es VERDADERA. Vamos a ver con más tranquilidad la sintaxis de la instrucción IF IF condición THEN instrucciones END IF SI la condición es VERDADERA, ENTONCES (THEN) se ejecutarán las instrucciones hasta llegar a END IF (FIN SI). Después se seguirá con el resto del programa Si la condición es falsa se saltará todo el bloque IF y se ejecutarán las siguientes instrucciones del programa que haya después del END IF. x < cc#!" 6!c En el ejemplo anterior puedes ver que las instrucciones que hay dentro del bloque IF están escritas un poco más a la derecha que las demás, no justo en el margen izquierdo. Esto se hace por comodidad, para poder saber de un vistazo cuales son las instrucciones que hay dentro del bloque. En la mayoría de lenguajes no es obligatorio (En COBOL sí), pero siempre es muy recomendable hacerlo así para dar mayor claridad al código fuente del programa. Para hacer esto basta con pulsar la tecla TABULADOR antes de escribir la instrucción, mejor que escribir espacios. En las opciones de QBasic se puede especificar el tamaño de la tabulación (Cuantos espacios se escriben). Por defecto tiene 8, pero es mejor un número más bajo entre 3 y 5 como ya veremos más adenante. Esta técnica también se conoce como encolumnado o indentación (En inglés se llama indent). x < cc!"&!$2"c Como hemos visto en la sintaxis de la instrucción IF, dentro del bloque de instrucciones a ejecutar se puede usar cualquier instrucción, por lo tanto también se pueden usar otras instrucciones IF. A esto es a lo que se llama anidación de instrucciones. Veamos un ejemplo que nos diga si un número es par (usando el operador MOD) y en este caso nos diga también si el número es mayor que 10: CLS INPUT "Escribe un número: ", num IF num MOD 2 = 0 THEN PRINT "Es un número par" IF num > 10 THEN PRINT "Es mayor que 10" END IF END IF
$c c
Ejemplos de la ejecución de este programa pueden ser... Escribe un número: 7 Escribe un número: 8 Es un número par Escribe un número: 12 Es un número par Es mayor que 10 Aquí si la primera condición se cumple se escribe "Es par" y además se hace otra comprobación que se podrá cumplir o no. Nunca se va a llegar a la segunda comprobación si la primera no se ha cumplido. Es muy importante observar que cada IF lleva su END IF correspondiente y que el bloque de instrucciones del segundo IF está encolumnado todavía más a la derecha que el anterior. Cuando escribimos un programa en papel es común unir con una línea cada IF con su END IF para aclarar el listado.
c x < /cc"#%($$2"c4c#'4$!&!c Si el bloque de instrucciones de una instrucción IF sólo va a llevar una instrucción podemos escribirla en la misma línea detrás del THEN y ahorrarnos el END IF. Para el ejemplo del examen podíamos haber hecho: CLS INPUT "Escribe la nota del examen: ", nota IF nota >= 5 THEN PRINT "Enhorabuena, has aprobado." PRINT "Gracias por usar el programa." Esto es útil en algunos casos, pero si vemos que nos vamos a liar es mejor poner el END IF como hemos visto antes, aunque si nos acostumbramos a poner siempre los encolumnados no hay por qué equivocarse. x < =cc!'%"!%3!c& ' c"#%($$2"c4%)"'#c En el ejemplo del examen estaría bien que "si no" aprueba, decirle que ha suspendido. Lo podemos hacer de esta forma: CLS INPUT "Escribe la nota del examen: ", nota IF nota >= 5 THEN PRINT "Enhorabuena, has aprobado." ELSE PRINT "Lo siento, has suspendido." END IF PRINT "Gracias por usar el programa."
c c
En este caso se comprueba la condición del IF. Si es verdadera se ejecuta su bloque de instrucciones y después se sigue con lo que venga detrás del END IF, igual que antes. Ahora viene lo nuevo, si la condición no se cumple se ejecuta el bloque de instrucciones del ELSE hasta el END IF, y después se sigue con lo que haya detrás. De esta forma nos aseguramos de que siempre se ejecuta uno y solo uno de los dos bloques de instrucciones según la condición sea verdadera o falsa. Dentro del bloque del ELSE también puede ir cualquier tipo de instrucción, incluido otro bloque IF o del tipo que sea. Veamos un ejemplo "especialmente malo" de un programa que nos diga el nombre de un mes a partir de su número. En temas posteriores simplificaremos bastante este problema. CLS INPUT "Escribe el número del mes: ", mes IF mes = 1 THEN PRINT "Enero" ELSE IF mes = 2 THEN PRINT "Febrero" ELSE IF mes = 3 THEN PRINT "Marzo" ELSE IF mes = 4 THEN PRINT "Abril" ELSE IF mes = 5 THEN PRINT "Mayo" ELSE IF mes = 6 THEN PRINT "Junio" ELSE IF mes = 7 THEN PRINT "Julio" ELSE IF mes = 8 THEN PRINT "Agosto" ELSE IF mes = 9 THEN PRINT "Septiembre" ELSE IF mes = 10 THEN PRINT "Octubre" ELSE IF mes = 11 THEN PRINT "Noviembre"
c c
ELSE IF mes = 12 THEN PRINT "Diciembre" ELSE PRINT "Mes no válido" END IF END IF END IF END IF END IF END IF END IF END IF END IF END IF END IF END IF PRINT "SRACABÓ" Muy sencillo: Si es enero lo escribimos, si no miramos si es febrero, si no a ver si es marzo, etc. así hasta diciembre. Si no es diciembre sacamos un mensaje de mes no válido. Observa que cada IF tiene su ELSE y su END IF correspondiente abajo. Normalmente nunca llegaremos a estas estructuras tan anidadas, pero aquí se puede ver la importancia de que el tamaño de tabulación no sea muy grande. Observa que si haces que la ventana del navegador sea más estrecha podría pasar (según los programas) que algunas líneas de las más largas pasan abajo y se nos estropea toda nuestra jerarquía. En QBasic y en los otros editores de programación esto no ocurre porque no tienen salto de línea automático para que no pase eso, pero de todas formas es muy incómodo tener que ir moviendo la barra de desplazamiento horizontal a izquierda y derecha para ver nuestro listado. x <
c c
ELSEIF mes = 3 THEN PRINT "Marzo" ELSEIF mes = 4 THEN PRINT "Abril" ELSEIF mes = 5 THEN PRINT "Mayo" ELSEIF mes = 6 THEN PRINT "Junio" ELSEIF mes = 7 THEN PRINT "Julio" ELSEIF mes = 8 THEN PRINT "Agosto" ELSEIF mes = 9 THEN PRINT "Septiembre" ELSEIF mes = 10 THEN PRINT "Octubre" ELSEIF mes = 11 THEN PRINT "Noviembre" ELSEIF mes = 12 THEN PRINT "Diciembre" ELSE PRINT "Mes no válido" END IF PRINT "SRACABÓ" A la Palabra Clave ELSE le hemos colocado directamente la otra condición para simplificar un poco el Algoritmo, pero viene a ser prácticamente lo mismo. Veamos la sintaxis de forma un poco más clara: IF condición THEN bloqueInstrucciones ELSEIF otra condición THEN bloqueInstrucciones ELSE bloqueInstrucciones END IF Si la condición del IF se cumple se ejecutan sus instrucciones y ya está. Si no, se comprueba la condición del primer ELSEIF y si es verdadera se ejecutan sus instrucciones y ya está. Si no, se comprueba la condición del siguiente ELSEIF y si es verdadera se ejecutan sus instrucciones y ya está. Si la condición del último ELSEIF no se cumple se ejecuta el bloque ELSE si existe. Puede haber tantos bloques ELSEIF con su condición como sea necesario, pero solo un bloque ELSE (o ninguno) al final. Si no hay bloque ELSE puede suceder que no se ejecute nada porque no se cumpla ninguna de las condiciones. Las condiciones no tienen que estar relacionadas de ninguna forma, aquí siempre hemos preguntado por el mes, pero podíamos haber comprobado cualquier otra cosa, o poner los meses desordenados.
c c
Esta estructura ELSEIF no se usa mucho y no existe en algunos otros lenguajes de programación, en su lugar se usan los IF anidados como vimos en el ejemplo larguísimo anterior o la estructura SELECT que veremos en el tema siguiente. Una última cosa antes de acabar con los IF. Si escribimos ENDIF todo junto, QBasic y Visual Basic nos lo corrigen automáticamente. Esto es porque en alguna versión muy antigua del lenguaje BASIC se escribía así.
c c
| c c | | c c c |c c
En el ejemplo de instrucciones IF anidadas nos salía un pedazo de listado para decir el nombre del mes. Después lo arreglamos un poco con las instrucciones ELSEIF. Ahora vamos a hacerlo todavía un poco mejor. CLS INPUT "Escribe el número del mes: ", mes SELECT CASE mes CASE 1 PRINT "Enero" CASE 2 PRINT "Febrero" CASE 3 PRINT "Marzo" CASE 4 PRINT "Abril" CASE 5 PRINT "Mayo" CASE 6 PRINT "Junio" CASE 7 PRINT "Julio" CASE 8 PRINT "Agosto" CASE 9 PRINT "Septiembre" CASE 10 PRINT "Octubre" CASE 11 PRINT "Noviembre" CASE 12 PRINT "Diciembre" CASE ELSE PRINT "Mes no válido" END SELECT PRINT "SRACABÓ" Hemos usado una estructura nueva: La instrucción SELECT. Esta estructura es equivalente a las anteriores de IF anidados, pero es más fácil de manejar y el programa queda más estructurado. Si has entendido la estructura IF no te será muy difícil entender esta. Lo primero es escribir las Palabras Clave SELECT CASE seguidas de una expresión. Esta expresión es normalmente simplemente una variable que puede ser de cadena o numérica. En este caso no tiene que devolver VERDADERO o FALSO como en los IF ya que no se usan operadores relacionales ni lógicos, solo los aritméticos cuando hace falta. A continuación para cada resultado posible se pone la Palabra Clave CASE y la expresión a comparar con la del principio. Si la comparación es verdadera se ejecuta el bloque de instrucciones entre
c c
este CASE y el siguiente y se sale de la estructura. Si la condición es falsa se ejecuta el bloque del CASE ELSE si existe y si no nada. Las expresiones de los cases se pueden poner de una de las siguientes formas: CASE 1 Una expresión (En este caso un número), igual que en el ejemplo. CASE 1, 2, 3 Varias expresiones separadas por comas. CASE 1 TO 3 Un intervalo de valores, ambos inclusive, separados por la palabra clave TO. En este caso si la expresión inicial era de tipo entero serán válidos los resultados 1, 2 y 3, pero si era de tipo real serán válidos todos los números posibles entre el 1 y el 3 como por ejemplo el 1.517512 y el 2.17521. CASE IS > 2 CASE IS = 5 CASE IS <> 8 CASE IS <= 6 Usando operadores relacionales. En el primer ejemplo serán válidos todos los valores mayores que 2, en el segundo sólo el 5, en el tercero cualquiera menos el 8 y en el último los que sean menor o igual que 6. Si se nos olvida la palabra clave IS, QBasic la escribirá por nosotros. Normalmente escribiremos las expresiones de los CASE de la forma más sencilla posible evitando intervalos muy extraños y teniendo cuidado con los operadores relacionales para no dejarnos "fuera" ningún valor posible, pero podemos llegar a escribir una estructura SELECT tan mal hecha como esta sin que el programa de ningún error de ejecución. SELECT CASE n CASE 1 CASE 1 CASE 3 CASE 2 TO 4 CASE IS < 5 CASE IS <= 5 CASE <> 100 CASE 16 TO 34 END SELECT Hay expresiones repetidas, valores que entran en varias expresiones como el 3, un desastre. ¿Que ocurriría aquí? Lo primero es que nunca ocurriría nada porque no hemos puesto bloques de instrucciones en los CASE, pero si las hubiéramos puesto pasaría lo siguiente. QBasic empieza a comprobar por los CASE hasta que encuentre uno que le venga bien. Cuando lo encuentra ejecuta su bloque de instrucciones y sale del SELECT CASE aunque otros bloques posteriores también hubieran servido. En este caso si el valor de n es 1 se ejecuta el primer CASE 1, el segundo no se llega a ejecutar nunca. Si el valor de n es 3 se ejecuta el CASE 3 y se sale, aunque
c c
los cuatro siguientes también hubieran servido. Si el valor de n es 100 no se ejecuta nada porque ningún CASE sirve y tampoco hay un CASE ELSE. Las expresiones de los cases normalmente serán simplemente números (o cadenas) y algún intervalo alguna vez para poder estar seguro de que se va a ejecutar siempre el bloque correcto y nuestro programa va a funcionar bien. Entre el SELECT CASE y el primer CASE no puede haber nada. Si los bloques de instrucciones van a llevar sólo una instrucción sencilla podemos ponerla a continuación de la expresión del CASE separándola con dos puntos, por ejemplo nuestro ejemplo de los meses quedaría así: SELECT CASE mes CASE 1: PRINT "Enero" CASE 2: PRINT "Febrero" CASE 3: PRINT "Marzo" CASE 4: PRINT "Abril" CASE 5: PRINT "Mayo" CASE 6: PRINT "Junio" CASE 7: PRINT "Julio" CASE 8: PRINT "Agosto" CASE 9: PRINT "Septiembre" CASE 10: PRINT "Octubre" CASE 11: PRINT "Noviembre" CASE 12: PRINT "Diciembre" CASE ELSE: PRINT "Mes no válido" END SELECT con lo que conseguimos un listado casi la mitad más corto. Por supuesto las expresiones también pueden ser de cadenas: INPUT "Escribe el nombre de un periférico del ordenador: ", perif$ SELECT CASE perif$ CASE "Teclado", "Ratón" PRINT "Es un periférico de entrada" CASE "Monitor", "Impresora" PRINT "Es un periférico de salida" CASE "Módem" PRINT "Es un periférico de entrada/salida" CASE ELSE PRINT "Este periférico no lo conozco" END SELECT Y por supuesto los bloques de instrucciones de los CASE pueden contener cualquier tipo de instrucciones anidadas en su interior como bloques IF, otro SELECT, etc. Si estos bloques se hacen muy largos no te asustes, solucionaremos el problema cuando lleguemos a Programación Modular donde el SELECT servirá como menú para dar entrada a distintos procedimientos o subprogramas.
"c c
Casi todos los lenguajes tienen una estructura equivalente a SELECT, pero en el caso del lenguaje C es bastante mala. Hay que tener en cuenta de que en los lenguajes de los sistemas de bases de datos (SQL) existe una instrucción SELECT que no tiene nada que ver con esto, sirve para sacar información de las bases de datos.
| c cc | | c c |c |c *c *c *c
x C xcc-c c c ccc. x C cc$-cc cD-ccc
x C cc-c4 "8%
c x C xcc-c c ccc. c En los siguientes temas vamos a ver las instrucciones que existen en la Programación Estructurada para conseguir que un bloque de instrucciones se puedan ejecutar más de una vez sin necesidad de escribirlas repetidas en el listado del código fuente del programa. En lenguaje ensamblador y en las versiones antiguas da BASIC se usan instrucciones de tipo GOTO que continúan la ejecución del programa en otra parte, pero esto da lugar a programas muy reliados (Código "espagueti") que son muy difíciles de depurar y pueden contener errores. Para solucionar el problema en la Programación Estructuradas existen estructuras de control que encierran un conjunto de instrucciones (con una instrucción al principio y otra al final) y lo que hacen es ejecutar el bloque de instrucciones entero un número determinado de veces, mientras se cumpla una condición o hasta que se cumpla una condición, según sea la estructura. A estas estructuras también se las conoce como "Bucles" o "Lazos". x C cc$-ccc c Al usar estas estructuras nos podemos encontrar con el problema de que si el programa no está escrito correctamente nunca se salga de la estructura de control produciéndose el efecto llamado "Bucle infinito" que puede llegar a bloquear el ordenador. En un programa normal ya compilado y terminado que se ejecute bajo Windows puede pasar que el ordenador se bloquee y aparezca una pantalla azul recuerdo de Bill Gates de tipo "El sistema está ocupado o no responde...", con lo que casi seguro que vamos a tener que reiniciar el ordenador. En los entornos de programación esto normalmente no llegará a ocurrir. En caso de que nuestro programa se bloquee puede que se agoten los recursos del sistema y el programa se detenga dando un error de tiempo de ejecución y volviendo al editor de código. Si el programa se queda bloqueado se puede pulsar la siguiente combinación de teclas: $cMc
Para detener la ejecución del programa y volver al editor de código donde habrá que repasar el código para que esto no ocurra y el
#c c
programa funcione siempre bien. En algunos casos tras pulsar esta combinación de teclas habrá que pulsar una vez la tecla ENTER para desbloquear el programa. x C cc-c4 "8%c Empecemos con un ejemplo como siempre. Vamos a escribir un programa que escriba los números del 1 al 5 usando las instrucciones que ya conocemos. CLS PRINT 1 PRINT 2 PRINT 3 PRINT 4 PRINT 5 Como se puede ver es un programa bastante tonto. Hay cinco instrucciones casi iguales. Solo cambia el valor de la expresión que cada vez vale lo que en la instrucción anterior más uno, por lo tanto también podíamos haber hecho esto: CLS n = 0 n = n + 1 PRINT n n = n + 1 PRINT n n = n + 1 PRINT n n = n + 1 PRINT n n = n + 1 PRINT n Se puede comprobar que el resultado es el mismo que en el programa anterior y ahora sí que tenemos cinco pares de instrucciones completamente idénticos. Vamos a hacer el mismo programa con la nueva instrucción FOR ("Para" en castellano): CLS FOR n = 1 TO 5 PRINT n NEXT Ya está. Mira que sencillo, pero ahora viene la explicación. Esto lo que hace es que se ejecute lo que hay entre el FOR y el NEXT cinco veces siguiendo estos pasos: *c La primera vez n vale 1, como pone en la instrucción. *c Se ejecuta el bloque de instrucciones con n valiendo 1 *c AUTOMÁTICAMENTE n se incrementa en 1, pasando a valer 2 *c Se comprueba que n es menor o igual que 5, y como lo es se sigue. *c Se ejecuta el bloque de instrucciones con n valiendo 2 *c AUTOMÁTICAMENTE n se incrementa en 1, pasando a valer 3
$c c
Se comprueba que n es menor o igual que 5, y como lo es se sigue. *c Se ejecuta el bloque de instrucciones con n valiendo 3 *c AUTOMÁTICAMENTE n se incrementa en 1, pasando a valer 4 *c Se comprueba que n es menor o igual que 5, y como lo es se sigue. *c Se ejecuta el bloque de instrucciones con n valiendo 4 *c AUTOMÁTICAMENTE n se incrementa en 1, pasando a valer 5 *c Se comprueba que n es menor o igual que 5, y como lo es se sigue. *c Se ejecuta el bloque de instrucciones con n valiendo 5 *c AUTOMÁTICAMENTE n se incrementa en 1, pasando a valer 6 *c Se comprueba que n es menor o igual que 5, y como ya no lo es se sale del bucle y se ejecuta la siguiente instrucción que venga detrás del NEXT. Todo esto puede parecer muy complicado, pero con la práctica conseguiremos que esta sea una de las instrucciones más fáciles de entender de la programación, sólo habrá que detenerse a pensar en estos pasos cuando algún programa no haga lo que queremos y no demos con el error. Veamos la sintaxis de la instrucción FOR: FOR contador = inicio TO final bloqueInstrucciones NEXT contador es la variable que usaremos como contador (el FOR la modifica automáticamente) y tendrá que ser de tipo numérico, normalmente entero aunque también puede ser real. Ya hemos hablado de los contadores en el tema de los operadores de asignación. inicio es una expresión numérica cuyo valor tomará el contador la primera vez. final es una expresión numérica cuyo valor lo usará el FOR de forma que solo entrará si el contador no supera al valor de esta expresión. En nuestro ejemplo el final era 5 y cuando el contador (n) llegaba a valer 6 ya no entrábamos. Ahora vamos a ver dos normas muy importantes que hay que seguir siempre con los contadores de los FOR *c No debemos modificar el valor de esta variable dentro del bucle, ya lo hace automáticamente la instrucción FOR. Dicho de otra forma: No debemos asignar ningún valor a esta variable hasta después de terminar el FOR. *c Una vez terminado el FOR no debemos leer el valor de la variable contador porque su valor queda indeterminado. Podremos usar esta variable más adelante si previamente le asignamos un valor antes de intentar leerla. Estas normas nos las podríamos saltar sin dar un error de ejecución, pero puede que el mismo algoritmo de distintos resultados en distintas versiones de BASIC, ya que el contador es manejado *c
c c
internamente por el intérprete del lenguaje de programación y puede que no siempre se haga de la misma forma. Los valores inicio y fin no tienen por que ser expresiones constantes. En este ejemplo escribiremos los números desde uno hasta donde quiera el usuario: CLS INPUT "Escribe hasta dónde quieres llegar: ", max FOR n = 1 TO max PRINT n NEXT No es necesario que tengamos que usar siempre el valor del contador para calcular algo. Este FOR escribe "Hecho en Ronda" siete veces: FOR n = 1 TO 7 PRINT "Hecho en Ronda" NEXT y este hace exactamente lo mismo: FOR n = 82 TO 88 PRINT "Hecho en Ronda" NEXT El siguiente escribe los pares del 2 al 10, es decir, 2, 4, 6, 8, 10. FOR n = 1 TO 5 PRINT n * 2 NEXT En QBasic hay una forma de hacer esto más fácilmente: FOR n = 2 TO 10 STEP 2 PRINT n NEXT Antes veíamos que el FOR incrementa automáticamente al contador en 1 en cada pasada. Usando la palabra clave STEP seguida de una expresión numérica conseguimos modificar este incremento. Otro ejemplo con STEP que se explica solo. CLS INPUT "Escribe un número: ", s PRINT "Estos son los números del 0 al 100 de "; s; " en "; s FOR n = 0 TO 100 STEP s PRINT n NEXT Todo esto funciona muy bien, espero que se entienda. Pero puede surgir una duda, supongamos que escribimos el número 7 y el programa escribe de siete en siete, dando este resultado: Escribe un número: 7 Estos son los números del 0 al 100 de 7 en 7 0 7 14 21 28 35 42
c c
49 56 63 70 77 84 91 98 Como se puede ver, no se ha alcanzado el 100 es porque el siguiente valor que sería el 105 ya supera al 100 que es valor final del FOR y no se ejecuta. También puede ocurrir que la expresión del STEP valga 0. En este caso el FOR incrementará en cero el contador con lo que nunca se llegará al valor final y se producirá un bucle infinito. Habrá que pulsar Ctrl+Pausa para detener el programa y corregir el código. Ahora ya podemos hacer que un FOR funcione hacia atrás, escribiendo la expresión final menor que la inicial y una expresión negativa en el STEP. Como ejemplo un FOR que escriba los números del 10 al 1. FOR n = 10 TO 1 STEP -1 PRINT n NEXT Si no usamos el STEP negativo y escribimos el valor final menor que el inicial, nunca se ejecutará el bloque FOR. Si un programa no funciona bien porque un FOR no se ejecuta nunca será conveniente revisar esto. Como siempre, dentro del bloque FOR puede ir cualquier tipo de instrucciones, incluido otro FOR. Veamos un ejemplo: FOR i = 1 TO 8 FOR j = 1 TO 5 PRINT "Hola" NEXT NEXT ¿Cuantas veces escribirá "Hola" este programa? Si el FOR interior se ejecuta entero 8 veces y cada vez escribe "Hola" 5 veces, en total lo hará 8 por 5 igual a 40 veces, es decir, el producto. Este tipo de instrucciones son especialmente útiles en algoritmos que ya veremos más adelante como el recorrido de matrices. Hay que tener cuidado de no usar la misma variable contador para los dos FOR, ya que romperíamos la regla de no modificar el valor del contador del primer FOR y el programa no funcionaría bien. QBasic permite escribir a continuación del NEXT el nombre del contador del FOR, por ejemplo: FOR i = 1 TO 8 FOR j = 1 TO 5 PRINT "Hola" NEXT j NEXT i
c c
Esto puede ser útil para saber en un listado muy complicado a que FOR corresponde cada NEXT, pero si encolumnamos correctamente nuestro programa esto no será necesario. El FOR en Basic es bastante flexible. En otros lenguajes funciona de otra forma o incluso puede que ni siquiera exista, ya que como veremos a continuación no es imprescindible para construir un algoritmo. c
| c Úc | | c c |c !
c
En este tema vamos a ver una estructura repetitiva más primitiva que el PARA ya que no maneja automáticamente el contador y por lo tanto es más difícil de utilizar, pero usada corréctamente puede ser bastante más flexible. Recordemos el ejemplo de escribir los números del 1 al 5 con la instrucción FOR. FOR n = 1 TO 5 PRINT n NEXT Ahora veremos cómo se hace lo mismo en QBasic usando la instrucción WHILE (Mientras). n = 1 WHILE n <= 5 PRINT n n = n + 1 WEND Esto lo que hace es ejecutar el bloque de instrucciones (Lo que hay entre el WHILE y el WEND) una y otra vez se cumpla la condición del WHILE. Un poco más difícil que con el FOR. Vamos a verlo paso a paso: *c Usaremos como contador la variable n y por tanto la tenemos que inicializar nosotros al valor que queramos que tenga la primera vez, en este caso 1. *c Se comprueba la condición del WHILE: como n vale 1 que es menor o igual que 5, entramos. *c Se ejecutan las instrucciones del bloque con n valiendo 1. La última instrucción incrementa n en 1, con lo que pasa a valer 2. *c Volvemos al WHILE y se comprueba su condición: como n vale 2 que es menor o igual que 5, entramos. *c Se ejecutan las instrucciones del bloque con n valiendo 2. La última instrucción incrementa n en 1, con lo que pasa a valer 3. *c Volvemos al WHILE y se comprueba su condición: como n vale 3 que es menor o igual que 5, entramos. *c Se ejecutan las instrucciones del bloque con n valiendo 3. La última instrucción incrementa n en 1, con lo que pasa a valer 4. *c Volvemos al WHILE y se comprueba su condición: como n vale 4 que es menor o igual que 5, entramos. *c Se ejecutan las instrucciones del bloque con n valiendo 4. La última instrucción incrementa n en 1, con lo que pasa a valer 5.
c c
Volvemos al WHILE y se comprueba su condición: como n vale 5 que es menor o igual que 5, entramos. *c Se ejecutan las instrucciones del bloque con n valiendo 5. La última instrucción incrementa n en 1, con lo que pasa a valer 6. *c Volvemos al WHILE y se comprueba su condición: como n vale 6 que ya no es menor o igual que 5, no entramos y pasamos a la siguiente instrucción que haya detrás del WEND. Se puede ver que el funcionamiento es parecido al del FOR, solo que aquí lo tenemos que controlar todo nosotros. Las dos reglas que dijimos sobre los contadores del FOR ya aquí no tienen sentido porque de hecho nosotros vamos a tener que incrementar el contador haciendo una asignación y una vez terminado podemos estar seguro del valor que tiene la variable. Una norma que sí conviene respetar (Aunque no siempre es necesario) es que la instrucción que incrementa el contador sea la última del bloque, ya que si está en otro sitio ejecutaremos unas instrucciones con un valor y las demás con el otro, con lo que nos podemos liar. Un error muy típico es que se nos olvide de poner la instrucción de incrementar el contador, produciendo un bucle infinito que hará que nuestro programa no termine nunca. Si un programa se bloquea es conveniente revisar esto. También puede pasar que no lleguemos a entrar al MIENTRAS porque la condición ya sea falsa la primera vez, por ejemplo: contador = 120 WHILE contador < 100 PRINT "Esto no se va a llegar a escribir nunca.´ contador = contador + 1 WEND Hasta ahora hemos hablado de contador, pero como veremos en los ejemplos podemos usar un acumulador, o ninguno de los dos, ya que la condición del WHILE puede ser cualquiera y no hay porqué contar ni acumular algo siempre. Veamos algunos ejemplos de MIENTRAS: Rutina que escribe del 0 al 100 de 2 en 2: c = 0 WHLE c <= 100 PRINT c c = c + 2 WEND Escribir de 50 hasta 1 hacia atrás: c = 50 WHILE c >= 1 PRINT c c = c - 1 WEND Calcular el factorial de un número que pedimos al usuario (Este ejemplo se mejorará en otro tema más adelante): INPUT " número para calcular su factorial: ", num c = 1 *c
c c
factorial = 1 WHILE c <= num factorial = factorial * c c = c + 1 WEND PRINT "El factorial de´; num; "es´; factorial Leer números por teclado hasta que se escriba el 0: INPUT "Escribe números (0 para salir):´, num WHILE num <> 0 INPUT "Escribe números (0 para salir):´, num WEND Este último ejemplo presenta lo que se conoce como lectura anticipada. Antes de llegar al WHILE hemos tenido que conseguir el valor de la variable num porque si no lo hacemos puede pasar que num valga cero y por lo tanto no lleguemos a entrar al bucle. Esto es útil en casos como la lectura de ficheros secuenciales, pero otras veces conviene evitarlo para no repetir instrucciones. Veremos como hacerlo en el siguiente tema. Una última cosa es que hay una teoría en informática que dice que cualquier algoritmo puede ser programado usando solamente instrucciones MIENTRAS. Es decir, ni bloques IF, ni ELSE, ni CASE, ni FOR, ni otras estructuras que veremos más adelante. Yo no lo he comprobado, pero si a alguien le ha gustado mucho este tema ya puede empezar a hacerlo.
c | c c | | c c |c c *c *c
x xx xcc c& ' x xx cc& -cc cc
x xx xcc#%($%(!c& 'c Esta estructura es similar a la WHILE, solo que la condición se especifica al final del bloque, con lo que puede ser más fácil de entender y nunca tendremos que hacer "lecturas anticipadas" ya que siempre se entra por lo menos una vez. La principal novedad de este bloque es que podemos repetir las instrucciones "%!# se cumpla la condición o )!#%! que se cumpla. El siguiente ejemplo escribe los números del 1 al 5 usando una instrucción DO...LOOP WHILE que hace que el bucle se ejecute MIENTRAS nuestro contador sea menor que 5. n = 0 DO n = n + 1 PRINT n LOOP WHILE n < 5 Observa que el contador se incrementa al principio del bucle, y por lo tanto la primera vez que escribamos n, ya tendrá el valor de 1. La
c c
última vez escribirá 5 y al llegar a la condición se comprobará que NO es menor que 5 y ya salimos. Ahora haremos lo mismo con la instrucción DO... LOOP UNTIL que ejecutará el bloque HASTA QUE el contador llegue a valer 5. n = 0 DO n = n + 1 PRINT n LOOP UNTIL n = 5 Esto es parecido. Observa que la condición es justo la contraria. La última vez n vale 5 y después de escribirla se comprueba que la condición es verdadera y se sale del bucle. Las instrucciones DO...LOOP UNTIL son normalmente las más fáciles de comprender. En la ayuda de QBasic recomiendan que se dejen de usar las instrucciones WHILE...WEND para usar mejor las DO...LOOP, pero algunas veces será mejor usar las WHILE...WEND como en el caso de los ficheros secuenciales que ya veremos más adelante. x xx cc&(!$2"c&c&!%#c&c"%!&!c Ahora ya estamos en condiciones de ver una forma de conseguir que un programa que requiere la intervención del usuario para introducir datos de entrada no avance HASTA QUE el usuario no escriba los datos correctos. En el tema de Entrada/Salida vimos que QBasic es capaz de controlar que no se metan valores fuera de rango, por ejemplo que si el programa pide un entero no se pueda escribir un numero mayor de 32767. Aquí lo que vamos a ver es como conseguir que se pedimos un mes el programa no avance hasta que el usuario escriba un número entre 1 y 12. Esto lo deberíamos hacer en cualquier programa siempre que pidamos al usuario que escriba algo. Para hacer este control lo que hacemos es meter la instrucción INPUT dentro de un bloque REPETIR, del que no salimos HASTA que la respuesta sea correcta o MIENTRAS sea incorrecta. Vamos a ver unos ejemplos que aclararán todas las posibles situaciones. Leer un número menor que 100 CLS DO INPUT "Escribe un número menor que 100: ",num LOOP UNTIL num < 100 Aquí no seguimos hasta que el número sea menor que 100. En el siguiente ejemplo seguiremos repitiendo la pregunta mientras el número sea mayor o igual que 100 CLS DO INPUT "Escribe un número menor que 100: ",num LOOP WHILE num >= 100
c c
Puedes volver a comprobar aquí que para el mismo problema la condición del LOOP WHILE es justo la inversa a la del LOOP UNTIL. Ahora pedimos un mes, que tiene que ser entre 1 y 12 CLS DO INPUT "Escribe un mes (0 a 12): ",mes LOOP UNTIL (mes >= 1) AND (mes <= 12) En este caso tenemos un intervalo y por lo tanto hay que controlar dos condiciones que uniremos con el operador AND, con lo que no seguimos HASTA que la primera se cumpla Y la segunda también. También lo podíamos haber hecho con un bloque DO...LOOP WHILE CLS DO INPUT "Escribe un mes (0 a 12): ",mes LOOP WHILE (mes < 1) OR (mes > 12) Ahora no salimos MIENTRAS alguna de las dos condiciones se cumpla, son justo las contrarias a la del ejemplo anterior, ya que usamos el operador OR que también se puede decir que es el contrario al AND. Por último vamos a ver un problema muy típico: Un programa que nos pide una clave de acceso para poder seguir. Este es el caso más sencillo en el que no pasamos hasta que no demos con la clave, otros problemas más complicados serían que el programa terminara tras varios intentos fallidos o que admitiera varias claves llévándonos según la que sea a una parte del programa. CONST ClaveCorrecta$="Ábrete Sésamo" CLS DO INPUT "Escribe la clave: ",clavePrueba$ LOOP UNTIL clavePruebas = claveCorrecta$ PRINT "Ya has entrado" Recordar también que en un programa terminado para explotación la clave nunca va a estar en el listado del programa, sino que será obtenida de algún tipo de fichero o base de datos para que el usuario tenga la posibilidad de cambiarla.
| c c
c *c *c *c *c *c *c *c
x x x x x x x
x x x x x x x
xcc-c cc I cc3 cc cc /cc! c cc Icc =cc c
c x x xcc"%&($$2"c!c'#c!!7#c Hasta ahora hemos usado variables para guardar la información que maneja el programa. Una variable con su nombre y su tipo para cada
"c c
dato (nombre, cantidad, nivel, precio, edad...) que nos hacía falta. Pero ¿Qué ocurre si nuestro programa tiene que manejar una colección de datos muy grande como los nombres de una agenda o los precios de un catálogo? Se podría hacer usando 100 variables distintas pero el algoritmo llegaría a ser terriblemente complicado y muy poco funcional. En este apartado vamos a ver cómo podemos asignar un mismo nombre de variable para muchos datos y referirnos a cada uno de ellos de forma individual usando un número al que llamaremos índice. Usaremos unas estructuras de datos llamadas arrays, de las que se dice que son "Estructuras estáticas de almacenamiento interno". *c Son estáticas porque su tamaño se declara al principio del programa y ya no se puede modificar. Si hacemos un programa que trabaje con 100 nombres siempre lo hará con 100 aunque solo lleguemos a usar 20, y nunca podrá trabajar con más de 100. *c Son de almacenamiento interno porque están en la memoria RAM del ordenador, como variables que son, y su tiempo de vida sólo dura mientras se ejecuta el programa. Al terminar se pierde su contenido. En programación normalmente se conocen como "Arrays" aunque su nombre más correcto en castellano sería "formaciones". Como veremos en los apartados siguientes, los arrays de 1 dimensión se llaman vectores, los de 2 dimensiones se llaman matrices y los de 3 o más se llaman poliedros o arrays multidimensionales. En informática muchas veces se llama vector a cualquier array. En la ayuda de QBasic (y de Visual Basic) siempre llaman Matriz a cualquier array, por lo que hay que tener cuidado de no liarse. x x cc3$%#c Imaginemos este bloque de pisos de cuatro plantas.
Vamos a escribir un programa de la forma que sabemos hasta ahora que nos pregunte el nombre de la persona vive en cada piso y una vez que ha recopilado toda esa información, nos deje preguntarle quien vive en el piso que nosotros queramos. CLS INPUT "Nombre de quien vive en el 1º: ", nombre1$
#c c
INPUT "Nombre de quien vive en el 2º: ", nombre2$ INPUT "Nombre de quien vive en el 3º: ", nombre3$ INPUT "Nombre de quien vive en el 4º: ", nombre4$ DO INPUT "Escribe un piso para ver quien vive en él: ",n LOOP WHILE (n < 1) OR (n > 4) SELECT CASE n CASE 1: PRINT "En el 1º vive "; nombre1$ CASE 2: PRINT "En el 2º vive "; nombre2$ CASE 3: PRINT "En el 3º vive "; nombre3$ CASE 4: PRINT "En el 4º vive "; nombre4$ END SELECT El resultado podría ser este: Nombre de quien vive en el 1º: Paca Nombre de quien vive en el 2º: Manolo Nombre de quien vive en el 3º: Lola Nombre de quien vive en el 4º: Pepe Escribe un piso para ver quien vive en él: 3 En el 3º vive Lola Un listado un poco largo para hacer algo tan sencillo. Si en vez de cuatro pisos fueran 40 tendríamos un programa casi diez veces más largo con muchas partes casi iguales, pero que no podemos meter en bucles repetitivos porque cada variable es distinta. Observa que comprobamos que el piso sea entre 1 y 4, a partir de ahora va a ser muy importante depurar los datos de entrada, ya veremos por qué. Ahora vamos a escribir un programa que haga lo mismo que el anterior, pero usando un VECTOR. DIM nombre$ (1 TO 4) FOR n = 1 TO 4 PRINT "Nombre de quien vive en el"; n; "º: "; INPUT "", nombre$(n) NEXT DO INPUT "Escribe un piso para ver quien vive: ",n LOOP WHILE (n < 1) OR (n > 4) PRINT "En el";n;"º vive ";nombre$(n) El resultado sería similar, pero el listado es mucho más corto, especialmente todo el SELECT CASE anterior que se ha transformado en una sola instrucción. Vamos a ver este programa línea a línea. La primera instrucción es nueva. En ella lo que hacemos es DECLARAR una variable que se va a llamar nombre$ (como lleva el $ va a ser de tipo texto) y va a poder guardar cuatro valores a los que accederemos con subíndices que van desde el 1 hasta el 4. Para determinar este intervalo de valores hay que usar números enteros constantes, no valen expresiones matemáticas. En el siguiente bloque FOR, que se ejecutará 4 veces lo que hacemos es ir pidiendo al usuario que escriba los nombres. La primera vez
$c c
guardamos lo que escriba en la posición 1 del vector porque hacemos referencia al índice 1 entre paréntesis a continuación del nombre del vector. La siguiente vez al índice 2, la siguiente vez al 3 y la última vez que se ejecute el bucle hacemos referencia al índice 4. A esto es a lo que se llama "Recorrer el vector" ya que hemos hecho algo con cada uno de sus elementos. Normalmente esto lo haremos siempre con un bucle FOR, que es lo más cómodo. La pregunta de "nombre de quien vive en..." no está en el INPUT porque esta instrucción no evalúa expresiones, por eso está antes en un PRINT normal con un punto y coma al final para que el cursor no pase a la siguiente línea. Ahora ya tenemos dentro del vector los cuatro nombres para usarlos como queramos haciendo referencia al nombre del vector y al subíndice entre paréntesis. Para hacer referencia a los subíndices de un array se puede usar cualquier expresión, no tiene porqué ser un número constante, pero hay que tener cuidado de no hacer nunca referencia a índices que no existan En el siguiente bloque pedimos al usuario que escriba el número de un piso y lo guardamos en la variable N. Obligamos a que sea un número entre 1 y 4. Al final viene lo espectacular. Para acceder a cualquiera de los índices del vector se puede hacer directamente tomando el valor de la variable N como subíndice sin necesidad de controlar cada valor por separado como antes. Vamos con otro ejemplo para que todo esto vaya quedando cada vez más claro. En temas anteriores teníamos un programa para escribir el nombre de un mes a partir de su número. Lo hicimos con muchos IF anidados, después con ELSEIF y por último con SELECT CASE que ya quedaba mucho más corto, pero de ninguna forma nos libramos de escribir todo el SELECT CASE y todos los meses en cualquier parte del programa dónde queramos que se escriba el nombre de algún més. Ahora vamos a plantear el problema de otra forma: DIM mese$ (1 TO 12) mese$(1)="Enero" mese$(2)="Febrero" mese$(3)="Marzo" mese$(4)="Abril" mese$(5)="Mayo" mese$(6)="Junio" mese$(7)="Julio" mese$(8)="Agosto" mese$(9)="Septiembre" mese$(10)="Octubre" mese$(11)="Noviembre" mese$(12)="Diciembre" R(...) PRINT mese$(2) REscribe Febrero R(...)
c c
n=6 PRINT mese$(n) REscribe Junio Hemos declarado un vector de cadenas de 12 posiciones llamado mese$. Al principio del programa llenamos el vector con los nombres de los meses, cada uno en su lugar correcto. Donde nos haga falta el nombre de un mes sólo tendremos que usar el vector y referirnos al mes que queramos. De esta forma meses(3) nos devolverá "Marzo" y si n vale 11 entonces meses(n) nos devolverá "Noviembre". En los dos ejemplos que hemos puesto los índices de los vectores han empezado en el 1, pero esto no tiene que ser siempre así. En QBasic pueden empezar por cualquier número incluso negativo, aunque lo más normal es que siempre empiecen por 1 o por 0. El valor más bajo posible para los índices es -32768 y el más alto es 32767. Por supuesto el final del intervalo no puede ser menor que el principio. Veamos algunos ejemplos más de declaración de vectores. DIM alumno$(1 TO 30) R30 cadenas DIM nota (1 TO 30) R30 números reales DIM ventas_verano%(6 TO 9) R4 enteros También podemos declarar vectores de un solo elemento, aunque esto puede que no tenga mucho sentido. DIM número (1 TO 1) Para determinar el tamaño de cualquier vector usamos la siguiente fórmula: ?cScccScMcxc Así en el ejemplo de los meses resulta 12 - 1 + 1 = 12 elementos, muy sencillo ¿no? pero a veces terminaremos contando con los dedos. Para inicializar un vector (Borrarlo entero) no hace falta recorrerlo, podemos hacer: ERASE (nombre_vector) Y el vector se quedará lleno de ceros si es numérico o de cadenas vacías ("") si es de cadenas. Recordemos que en QBasic todas las variables están a cero al principio, pero en otros lenguajes no. Ahora vamos a ver el problema más típico de los vectores (y en general de todos los arrays). No podemos intentar acceder a subíndices que no existen. En caso de que llegue a ocurrir en QBasic se producirá un error de tipo "Subíndice fuera del intervalo" y el programa se detendrá sin mayores consecuencias. En muchos otros lenguajes no ocurrirá nada, pero si estamos leyendo sacaremos datos de otras posiciones de memoria que no son nuestras de más allá del final del vector y si estamos escribiendo lo haremos sobre escribiendo otros datos importantes para el programa o incluso para el sistema operativo (DOS o WINDOWS) con lo que casi seguro conseguiremos que el ordenador se quede bloqueado. Imagina que en un programa (No de QBasic ocurre esto y va a parar
c c
a un registro estratégico del sistema operativo un número que equivale de alguna forma a la llamada al programa de formatear el disco duro, no veas el estropicio. De ahí la importancia de depurar los datos de entrada, especialmente los que van a servir como índices para arrays. Si en nuestro primer ejemplo no depuramos el dato del piso que pedimos al usuario y este escribe uno que no está entre cero y cuatro, al acceder al vector se produciría este error. x x cc!%$#c Ahora imaginemos este otro bloque de pisos, para el que tenemos que hacer un programa similar al anterior.
Vamos a escribir el programa. Será igual que el anterior, habrá que controlar las cuatro plantas pero además dentro de cada una habrá que controlar las tres puertas que hay en cada rellano (1ª, 2ª y 3ª). DIM nombre$ (1 TO 4, 1 TO 3) FOR piso = 1 TO 4 FOR puerta = 1 TO 3 PRINT "Nombre de quien vive en el"; piso; "º"; puerta; "ª: "; INPUT "", nombre$(piso, puerta) next NEXT PRINT "Para saber quien vive en un piso..." DO INPUT " Escribe el piso: ",piso LOOP WHILE (piso < 1) OR (piso > 4) DO INPUT " Escribe la puerta: ",puerta LOOP WHILE (puerta < 1) OR (puerta > 3) PRINT "En el";piso;"º";puerta;"ª vive ";nombre$(piso, puerta) Se puede ver que es muy parecido, pero hemos utilizado una MATRIZ, que es un array de dos dimensiones, mientras que un vector es un array de una sola dimensión. Por si hay alguna duda de lo que hace este programa vamos a ver un posible resultado.
c c
Nombre de quien vive en el 1º 1ª: Paca Nombre de quien vive en el 1º 2ª: Gloria Nombre de quien vive en el 1º 3ª: Fernando Nombre de quien vive en el 2º 1ª: Mari Nombre de quien vive en el 2º 2ª: Juan Nombre de quien vive en el 2º 3ª: Manolo Nombre de quien vive en el 3º 1ª: Lola Nombre de quien vive en el 3º 2ª: Rosa Nombre de quien vive en el 3º 3ª: Mario Nombre de quien vive en el 4º 1ª: Pepe Nombre de quien vive en el 4º 2ª: Nacho Nombre de quien vive en el 4º 3ª: Luisa Para ver quien vive en un piso... Escribe la planta: 3 Escribe la puerta: 2 En el 3º 2ª vive Rosa Lo más novedoso es la forma de declarar la matriz, igual que el vector pero esta vez habrá que usar dos intervalos separados por una coma. Y por lo tanto siempre que nos refiramos a la matriz habrá que usar dos subíndices. Para recorrer la matriz hay que usar dos FOR anidados. Esta vez la hemos recorrido por filas (pisos en nuestro ejemplo) ya que el "FOR piso" está fuera y hasta que no se ejecute entero el "FOR puerta" en cada piso no pasamos al siguiente. Para recorrerla por columnas (puertas) bastaría con intercambiar los FOR: FOR puerta = 1 TO 3 FOR piso = 1 TO 4 R(...) next NEXT Para el ordenador es completamente intrascendente que lo hagamos de una forma u otra, él no entiende de filas horizontales ni columnas verticales, de hecho almacena todos los elementos seguidos uno detrás de otro y hace operaciones matemáticas con los dos subíndices para determinar la única posición del elemento. Esto se puede ver fácilmente pensando en la posición de los buzones de correos en el portal del bloque de pisos.
Cada piso tiene su buzón, el del 2º 2ª es el quinto porque tiene delante los tres de la primera planta y es el segundo de la segunda. Para calcular las posiciones se hace algo así como ((PLANTA1)*Nº_total_de_PUERTAS)+PUERTA, que sale ((2-1)*3)+2 = 5. A nosotros esto no nos interesa porque lo hace QBasic automáticamente. Si programamos en otros lenguajes más primitivos sí que habría que preocuparse de esto porque sólo existen vectores de una dimensión.
c c
x x cc'&#c Lo más normal es usar vectores (1 dimensión) o matrices (2 dimensiones), pero QBasic puede llegar a manejar arrays con hasta 60 dimensiones!!! Veamos un caso rebuscado de un array de tres dimensiones ampliando los ejemplos anteriores de los bloques de pisos.
En este caso nuestro bloque tiene tres portales (1 a 3) y cada uno de ellos tiene cuatro plantas y tres puertas igual que antes. Para declarar el poliedro habría que hacer: DIM nombre$ (1 TO 3, 1 TO 4, 1 TO 3) La primera dimensión va a ser para los portales, la segunda para los pisos y la tercera para las puertas. Para recorrer el array haríamos: FOR portal = 1 TO 3 FOR piso = 1 TO 4 FOR puerta = 1 TO 3 R(...) NEXT NEXT NEXT Esta vez tenemos tres bucles FOR anidados y las instrucciones que pongamos dentro de todo se ejecutarán 36 veces que es el producto de todas las dimensiones. Ya empezamos a liarnos pensando en recorrer el array primero por portales o no, etc... Todavía podemos tener una representación visual del problema, pero si usamos arrays de cuatro, cinco o más dimensiones ya esto no será así y la habremos liado del todo. En estos casos lo mejor será plantear el problema de otra forma o usar estructuras de datos más flexibles que ya veremos más adelante. c x x /cc!)!c#!$c"c'#c!!7#c&c$!&"!#c Hasta ahora no hemos sido demasiado estrictos en el ahorro de memoria ya que ni siquiera estamos declarando las variables. Nuestros programas de QBasic son muy sencillos y usan muy pocas variables, pero al trabajar con arrays hay que darse cuenta que basta
c c
con declarar una matriz de 20 por 20 elementos para que se gasten 400 posiciones de memoria. En las cadenas QBasic no nos limita el número de caracteres que vamos a poder meter en ellas y para que esto funcione se gasta cierta cantidad de memoria (unos 10 bytes), que si multiplicamos por 400 posiciones de memoria son casi 4 KB, una cantidad respetable para programas tan pequeños. Puede ocurrir que en nuestra matriz de cadenas no lleguemos a almacenar palabras más largas de por ejemplo 15 letras con los que nos convendría limitar el tamaño de las cadenas y ahorrarnos esos 10 bytes en cada posición. Para declarar un array de cadenas de tamaño limitado haríamos: DIM matriz(1 TO 20, 1 TO 20) AS STRING * 15 Las palabras clave AS STRING declaran explícitamente la variable como de tipo cadena (Esta será la forma normal de hacerlo en Visual Basic), por eso ya no es necesario poner el $ al final del nombre. Si a continuación ponemos un asterisco y un número estamos limitando el tamaño de las cadenas con lo que QBasic ya no tendrá que determinarlo automáticamente constantemente, con lo que ahorraremos memoria y el programa funcionará algo más rápido. Si asignamos a estas variables una cadena de más de 15 caracteres no ocurre nada, simplemente la cadena se corta y sólo se almacenan los primeros 15 caracteres, los demás se pierden. En los tipos numéricos no podemos ahorrar nada, pero en los arrays de cadenas deberíamos hacer siempre esto, especialmente si van a contener muchos elementos. x x =cc!%$#c'!&!#c Imaginemos el juego del buscaminas en el que tenemos una matriz de 8 por 8 elementos (Casillas) que van a contener un 1 si contienen una mina o un cero si están vacías.
Cuando el usuario destape una casilla, si no contiene una mina, habrá que escribir el número de minas que la rodean. Para hacer esto habrá que sumar los valores de las casillas que hay arriba a la izquierda, arriba, arriba a la derecha, a la derecha, abajo a la derecha, abajo, abajo a la izquierda y a la izquierda. Si estamos en una casilla del interior del tablero esto funciona perfectamente, pero si estamos en el borde nos encontramos con el problema de que haríamos referencia a posiciones de fuera de la matriz con lo que se produciría un error de "Subíndice fuera del intervalo".
c c
Para evitar esto habría que comprobar que si estamos en el borde superior no se cuenten las casillas superiores, si estamos en el inferior no se cuenten las de abajo, si estamos en una esquina solo se cuenten las tres de dentro, etc. con lo que tendríamos en total nueve rutinas diferentes, así como que controlar cual de ellas usar según por donde esté la casilla que estamos destapando. Para solucionar este problema lo que se suele hacer es dotar a la matriz de más filas y columnas de forma que todos los elementos que realmente nos interesan estén rodeados por un "borde" de elementos vacíos que estarán ahí sólo para poder hacer referencia a ellos sin salirnos de la matriz. Una representación gráfica de la matriz orlada podría ser esta, donde hay un borde de "casillas" que no vamos a usar nunca para poner minas, pero a las que podemos referirnos siempre que sea necesario sin salirnos de la matriz. En el ejemplo que nos ocupa, estas posiciones contendrían el valor 0 ya que están vacías.
Esta técnica se aplica mucho en juegos de este tipo, en programas de imágenes y en cualquier lugar donde se tenga que acceder a posiciones de una matriz y puedan producirse problemas en los bordes. En el caso de vectores habría que añadir un elemento más al principio y otro al final, para los poliedros sería como envolverlo completamente y en el caso de arrays de más de cuatro dimensiones ya es difícil imaginarse como se haría. Hay que recordar que en QBasic se pueden usar subíndices negativos, pero en la mayoría de otros lenguajes de programación esto no es así. El único inconveniente de las matrices orladas es que ocupan más memoria, exactamente (2*(alto + ancho))+4 posiciones más. Este gasto de recursos está justificado en la mayoría de los casos ya que se consigue simplificar mucho los programas. x x
c c
durante la ejecución del programa sin estar definido de ninguna forma en el código del programa. Por eso se dice que son estructuras de almacenamiento dinámicas. Como QBasic (ni Visual Basic) no trabaja directamente con punteros no podemos llegar a utilizar estas estructuras. En algún caso puede suceder que necesitemos almacenar elementos en un array (Vector, matriz, etc...), pero no podamos saber de antemano cuanto grande va a tener que ser y tampoco nos merezca la pena poner un límite arbitrario que se alcanzará en seguida o bien no llegará a ser usado nunca desperdiciando mucha memoria. Para solucionar en parte este problema QBasic nos da la posibilidad de declarar los arrays usando la instrucción REDIM en lugar de DIM y después en cualquier parte del programa poder redimensionarlos tantas veces como queramos usando valores que pueden ser variables. Veamos un ejemplo: REDIM mat(1 TO 1) INPUT "Cuantos números vamos a almacenar?", maxi REDIM mat(1 TO maxi) FOR n = 1 TO maxi INPUT mat(n) NEXT INPUT "Cuantos números vamos a almacenar ahora?", maxi2 REDIM mat(1 TO maxi2) FOR n = 1 TO maxi2 INPUT mat(n) NEXT Se puede ver que declaramos un vector de enteros de sólo un elemento usando REDIM, a continuación pedimos al usuario que escriba un número y después volvemos a redimensionar el vector con este número de elementos para ya recorrerla como hemos hecho siempre. A continuación repetimos el proceso volviendo a redimensionar el vector, perdíendose la primera serie de números que almacenó en ella el usuario. Aquí hay que aclarar algunas cosas: *c La primera orden REDIM mat(1 to 1) nos la podíamos haber ahorrado, pero siempre es costumbre declarar las variables al principio del programa para tener una visión global de las variables que hay sin tener que recorrer el listado completo. *c Si al declarar un array con la orden DIM usamos variables en vez de valores constantes lo declaramos como dinámico y después podrá ser redimensionado. *c Para redimensionar un array dinámico ya existente habrá que hacerlo siempre con la orden REDIM y en QBasic tendremos que usar las dimensiones que ya tiene, es decir, si antes era una matriz con dos dimensiones al redimensionarlo tenemos que especificar dos intervalos, ni más ni menos, y por supuesto el mismo tipo de datos.
"c c
Al redimensionar un array se borran todos los valores que tuviera, quedando entero inicializado a valores cero o cadenas vacías. Visto esto sólo queda por decir que siempre que sea posible se evite el uso de arrays dinámicos ya que el manejo interno de la memoria es mucho menos eficiente y podría dar lugar a errores como los típicos de "Memoria agotada". Para evitar que los arrays sean dinámicos usar siempre valores constantes (números) para definir sus dimensiones al declararlos con la orden DIM al principio del programa. *c
| c c c c *c *c *c *c *c *c *c *c *c *c *c
x x x x x x x x x x x
x x x x x x x x x x x
xcc-c c c -c cc' c -c cc cc' c ccIcJ cc&J-cc /cc3 c =cc3 c
c x x xcc"%&($$2"c!c'!c !!$2"c&('!c Hasta ahora hemos visto programas muy sencillos que simplemente eran una serie de instrucciones colocadas una detrás de otra en un listado. Esto va muy bien para nuestros programas extremadamente simples, pero conforme los proyectos se hacen algo más grandes van apareciendo una serie de problemas. *c Si hay partes que se repiten en distintos sitios la única solución es repetir el trozo de código correspondiente dónde haga falta. *c Si damos con un error en una de estas partes repetidas habrá que corregirlo en todas, sin olvidar ninguna. *c Si el programa es desarrollado por varias personas será muy difícil que puedan trabajar a la vez. *c El programa queda muy mal estructurado ya que está "todo seguido" y no se diferencian unas partes de otras. *c Etc. Para ir solucionando estos problemas se ha desarrollado lo que se conoce como programación modular y que consiste en dividir un programa en módulos o subprogramas más pequeños que realizan una acción determinada y son llamados por los otros módulos cada vez que haga falta. Esta forma de trabajar nos proporciona las siguientes ventajas, que solucionan los problemas anteriores. *c No habrá partes repetidas en los programas.
#c c
Al solucionar un posible error en el módulo correspondiente este queda solucionado de forma definitiva en todos los otros módulos que usaran este módulo. *c Un programa puede ser desarrollado por varias personas o en tiempos diferentes ya que cada uno puede desarrollar su módulo y al final solo habrá que ponerlos en común para que todo funcione. *c Los programas quedan muy bien estructurados y el código fuente se hace más legible ya que está formado por partes pequeñas unidas. Imaginemos por ejemplo en Windows los cuadros de diálogo que aparecen para abrir y guardar los archivos en los programas. ¿Has observado que casi siempre son iguales?. Esto es porque son módulos ya programado que se encuentran en una biblioteca de enlace dinámico (DLL) y son llamados por los programas. De esta forma el programador no se tiene que preocupar de programar las listas, los iconos, los botones, etc... sino que estos ya han sido programados anteriormente por otras personas y sólo hay que llamarlos desde nuestro programa de Windows. Además si se encuentra algún error basta con corregirlo y sustituir la DLL correspondiente (Esto es lo que hace constantemente Microsoft con sus "Service Packs" y sus "Segundas ediciones") para que todos los programas que la utilicen queden inmediatamente arreglados. *c
x x cc'!c !!$2"c&('!c"c !#$c La programación en Windows se basa en las DLL y por lo tanto en la programación modular. En los lenguajes más avanzados como C++ o Pascal se pueden usar bibliotecas de funciones que son llamadas desde nuestro programa y son enlazadas en el momento de compilar nuestro ejecutable EXE. En QBasic, al ser un lenguaje interpretado nos tenemos que limitar a usar módulos o más concretamente procedimientos que estén dentro del único fichero BAS que compone nuestro programa. Las posibilidades son muy limitadas pero nos van a servir para aprender los conceptos más importantes de esta técnica. Hasta ahora hemos escrito las instrucciones en el editor de código de Qbasic. Esta parte del programa es el "Módulo Principal" que debe existir siempre y desde donde se llamará a los distintos módulos del programa, si existen. En QBasic existen dos tipos de módulos: procedimientos SUB y funciones. *c Los procedimientos SUB son partes del programa que simplemente "hacen algo". *c Las funciones son partes del programa que al llamarlas hacen un cálculo y nos devuelven un valor. c x x cc''!!&!c!c$&"%#c7c4("$"#c Ya hemos usado sin saberlo procedimientos y funciones ya incluidas en el lenguaje de QBasic. Por ejemplo al usar la instrucción PRINT lo
$c c
que hacemos es llamar a un procedimiento ya programado dentro de QBasic que se encargará de dibujar caracteres en la pantalla, o al usar la función SQR dentro de una expresión conseguimos calcular la raíz cuadrada de un número sin tener que programar en ningún sitio las operaciones necesarias para hacer esto. La diferencia importante entre la llamada a los procedimientos y a las funciones es que para llamar a un procedimiento se escribe su nombre al principio de la línea, mientras que las llamadas a funciones van dentro de expresiones o bien formando parte de condiciones. Si están en una asignación, las llamadas a funciones siempre van a la derecha del signo "igual". También es muy importante darse cuenta de que los parámetros, si los hay, que se pasan a las funciones van entre paréntesis, mientras que los de los procedimientos no es obligatorio que vayan entre paréntesis, ya veremos por qué. Veamos algunos ejemplos, donde PRINT es un procedimiento y SQR es una función. PRINT "Hola" Llamamos a PRINT y le pasamos como parámetro "Hola". PRINT escribirá "Hola" en la pantalla, pero no devuelve ningún valor. raiz = SQR(n) Llamamos a la función SQR y le pasamos como parámetro la variable n. El resultado devuelto lo asignamos a la variable raiz. Observa que SQR está a la derecha del signo igual y que el parámetro n va entre paréntesis. PRINT SQR(4) Aquí llamamos a SQR para que nos calcule la raíz de 4, y el valor devuelto por la función se lo pasamos a PRINT para que lo escriba en la pantalla. DO ... LOOP WHILE SQR(n)>10 ... IF SQR(n)>50 THEN ... END IF En estos dos ejemplos usamos los resultados de la función como parte de la expresión que se usará como condición en instrucciones repetitivas y condicionales. A = PRINT(b) Esto se ve muy raro a primera vista ¿Cómo le asignamos a una variable lo que escriba PRINT en la pantalla?. Esto no se puede hacer, está mal escrito. Las llamadas a procedimiento siempre van al principio de la línea. SQR(4) Aquí se produce un error al pasar de línea porque llamamos a la función, pero cuando nos devuelve el resultado no hacemos nada con él porque la función ni está en una asignación ni en una condición.
c c
x x cc&4"$2"c&c$&"%#c Hasta ahora hemos llamado a procedimientos que ya están inluídos en el lenguaje de QBasic, pero para poder utilizar los nuestros propios además de llamarlos hay que programarlos. Todo el código que hemos escrito hasta ahora en nuestros programas ha estado en el "Módulo Principal" del programa. Para definir un procedimiento basta con seleccionar "Nueva sub" en el menú "Edición" de QBasic. Entonces aparecerá un pequeño cuadro de diálogo donde tendremos que escribir el nombre del procedimiento y pulsar Enter. Después de hacer esto desaparecerá de la pantalla nuestro módulo principal y en su lugar aparecerá algo así como esto. SUB Prueba END SUB Esto es el principio y el final del procedimiento. Entre estas dos líneas será donde tendremos que escribir todo el código. Fuera lo único que está permitido son comentarios. Siempre es muy recomendable escribir una pequeña descripción de lo que hace el procedimiento. También es normal escribir los nombres de los procedimientos con la primera letra en mayúsculas. Para elegir los nombres deberemos elegir un nombre descriptivo y tendremos las mismas restricciones que con los nombres de variables (Sin acentos ni ñ, máximo 40 caracteres, etc...). Para volver al módulo principal o para pasar de un procedimiento a otro tendremos que pulsar la tecla F2 y en la lista que aparece elegir el procedimiento al que queremos ir. Podemos entrar y salir de los procedimiento todas las veces que sea necesario mientras estamos escribiendo el código fuente del programa. Vemos con un ejemplo paso a paso. Vamos a hacer un programa que contenga un pequeño procedimiento para escribir un rótulo en la pantalla. Al usar programación modular podemos llamar al procedimiento cada vez que queramos escribir el rótulo en lugar de tener que repetir todo el código. Allá vamos. Abrimos un nuevo programa y empezaríamos a escribir lo que va en su módulo principal. En este caso no vamos a escribir todavía nada. Ahora vamos a definir el procedimiento que se va a llamar EscribeRotulo. Para hacerlo seleccionamos "Nueva sub" en el menú "Edición". Tras escribir el nombre y pulsar Enter aparecerá en la pantalla esto. SUB EscribeRotulo END SUB Ahora vamos a escribir las instrucciones que necsitamos para escribir el rótulo dentro de las dos líneas que ha escrito QBasic. SUB EscribeRotulo PRINT "******************" PRINT "* HECHO EN RONDA *" PRINT "******************" END SUB
c c
También podemos añadir en el encabezado una descripción como comentario. REste procedimiento escribe "Hecho en Ronda" en Rla pantalla rodeado de asteriscos. R SUB EscribeRotulo PRINT "******************" PRINT "* HECHO EN RONDA *" PRINT "******************" END SUB Ahora, para volver al módulo principal pulsamos F2 y seleccionamos el primer nombre de la lista que será "Sin_nombre" si todavía no hemos guardado el programa, o el nombre del fichero si ya lo hemos guardado. En el módulo principal bastará con escribir EscribeRotulo que es la llamada al procedimiento para que este se ejecute. En un programa con más instrucciones podemos llamar a los procedimientos todas las veces que haga falta escribiendo su nombre, y por su puesto podemos llamar a un módulo desde dentro de otro. Si abres el fichero del programa (*.BAS) con el editor de MS-DOS o con el Bloc de notas de Windows verás que los módulos están realmente a continuación del módulo principal. Al abrir los programas en QBasic se van separando para no tener listados tan largos y poder pasar fácilmente de uno a otro pulsando F2. x x /cc3!! '#c'$!'#c En nuestro pequeño procedimiento no hemos utilizado ninguna variable, simplemente nos limitamos a escribir un rótulo en la pantalla. Ahora vamos a ver un pequeño programa con un módulo en el que se sumarán los valores de dos variables. Recuerda que aunque aquí aparece todo seguido en QBasic sólo verás el programa principal y tendrás que pulsar F2 para acceder al procedimiento. CLS a = 2 b = 4 PRINT a + b MiProcedimiento RAquí llamamos al procedimiento PRINT a + b R REste procedimiento asigna valores a las variables a y b Ry muestra en pantalla la suma. SUB MiProcedimiento a = 8 b = 3 PRINT a + b END SUB Veamos ahora el resultado que se produciría en la pantalla al ejecutar el programa: 6
c c
11 6 Extraño ¿No?. Al principio la suma vale 6, después dentro del procedimiento vale 11 con los nuevos valores y después fuera por arte de magia vuelve a valer 6!!! Veamos lo que hace el programa paso a paso: *c Borra la pantalla, como siempre. *c Asigna a la variable a el valor de 4 *c Asigna a la variable b el valor de 2 *c Muestra en la pantalla la suma de a más b que es 6 *c Entra en el procedimiento, donde: dc Asigna a la variable a del procedimiento el valor de 8 dc Asigna a la variable b del procedimiento el valor de 3 dc Muestra en la pantalla la suma estas dos variables que es 11 dc Sale del procedimiento y sigue con el programa principal *c Muestra en pantalla la suma de las variables a y b del programa principal que sigue siendo 6 porque sus valores no han podido ser modificados desde el procedimiento Aquí se puede ver fácilmente que las variables son "locales" al módulo donde son declaradas (Recuerda que QBasic las declara automáticamente al usarlas la primera vez), ya sea este el módulo principal, un procedimiento SUB o una función, por lo tanto son variables totalmente distintas aunque tengan el mismo nombre. Para evitar confusiones se recomienda no repetir los nombres de las variables en los distintos módulos. En los temas anteriores decíamos que el tiempo de vida de una variable comienza cuando es declarada (En Qbasic, cuando se usa por primera vez), y termina cuando el programa termina, liberándose la posición de memoria que utilizaba y perdiéndose su valor, por lo que no se conserva para la próxima vez que ejecutemos el programa. Con las variables locales de los procedimientos ocurre lo mismo, desaparecen al terminar la ejecución del procedimiento y por lo tanto normalmente no se puede esperar que conserven su valor la próxima vez que llamemos al procedimiento, aunque sea durante la actual ejecución del programa. En QBasic para evitar esto y que las variables conserven su valor entre las distintas veces que llamemos a los procedimientos se dispone de la instrucción STATIC que nos permite declarar variables dentro de módulos de forma que conserven su valor en sucesivas llamadas. Para más detalles sobre esta instrucción consulta la ayuda de QBasic. Si planificamos bien la estructura de nuestro programa normalmente no será necesario utilizar esta instrucción. x x =cc3!! '#c ' !'#c En algunas situaciones nos puede interesar que una variable "pueda ser vista" por todos los procedimiento que integran nuestro programa. Esta variable la declararemos de una forma especial al
c c
propicio del módulo principal del programa y ya podrá ser usada (leída o modificada) por el propio módulo principal o por cualquier otro procedimiento. Veamos el mismo programa del tema anterior, pero esta vez usando variables globales: DIM SHARED a,b CLS a = 2 b = 4 PRINT a + b MiProcedimiento RAquí llamamos al procedimiento PRINT a + b R REste procedimiento asigna valores a las variables a y b Ry muestra en pantalla la suma. SUB MiProcedimiento a = 8 b = 3 PRINT a + b END SUB Ahora el resultado sería: 6 11 11 Se puede ver que el código es muy parecido, salvo la (gran) diferencia de que declaramos al principio del módulo principal las variables a y b usando la instrucción DIM SHARED. DIM es para declarar variables y SHARED para que sean globales (compartidas). En la primera parte del programa asignamos los valores a las variables y mostramos la suma. Después llamamos al procedimiento que modifica los valores de las variables a y b, que en este caso son las mismas que las del módulo principal, y muestra la suma, que es 11. Al salir del procedimiento, ya en el módulo principal se muestra la suma que sigue siendo 11 porque el procedimiento ha modificado las variables globales. Las constantes definidas en el módulo principal siempre serán globales, mientras que las que definimos dentro de un procedimiento SUB o función serán locales, solo visibles dentro del mismo igual que las variables locales. x x
c c
Veamos como ejemplo un procedimiento que acepta como parámetros dos enteros y escribe en la pantalla el mayor de ellos. Le vamos a llamar EscribeElMayor. También está aquí la llamada que se hace desde el módulo principal. RPrincipal CLS EscribeElMayor 4, 7 REscribe en pantalla sólo el número mayor de los dos parámetros RDatos de entrada: N1, N2: Reales SUB Escribe el Mayor(N1, N2) IF N1 > N2 THEN PRINT N1 ELSE PRINT N2 END IF END SUB Vamos a ver con detalle la primera línea de la definición del procedimiento. Usamos la palabra SUB seguida del nombre del procedimiento y a continuación viene lo nuevo. Como antes no usábamos parámetros no poníamos nada más, pero como ahora tenemos dos parámetros, los DECLARAMOS entre paréntesis. Los parámetros son variables que serán manejadas desde dentro del módulo, y por lo tanto igual que las otras variables normales tienen su tipo de datos y un nombre que debe de seguir las mismas normas de construcción. En este caso como no hemos usado sufijos se entiende que son reales de precisión sencilla, si hubiéramos puesto al final del nombre un % serían enteros, o de cadenas si hubiéramos puesto un $. Un módulo (sea un procedimiento o una función que ya veremos más adelante) puede llevar tantos parámetros como haga falta, o ninguno, y estos no tienen por que ser del mismo tipo. Lo importante es que a la hora de llamar al módulo tenemos que escribir a continuación del nombre constantes o expresiones que encajen en número y tipo con los parámetros que acepta el módulo. En este ejemplo se esperan dos números, por lo tanto habrá que poner exactamente dos expresiones numéricas, ni más ni menos, y tampoco podemos usar cadenas en este caso. En el ejemplo pasamos como parámetros en la llamada al procedimiento dos expresiones constantes y por lo tanto no debe de haber ninguna duda, pero cuando pasamos como expresión una variable pueden pasar algunas cosas algo inesperadas. Veamos otro ejemplo. RPrincipal CLS n = 4 PRINT n MultiplicaPorDos n
c c
PRINT n RMódulo Multiplica por dos RMultiplica por dos el argumento RDatos de entrada: numero: Real que se multiplicará por dos SUB MultiplicaPorDos (numero) numero = numero * 2 END SUB El resultado sería: 4 8 Es fácil de entender: En el programa principal se asigna a n un valor, a continuación se llama a un procedimiento que toma n como entrada, aunque dentro se llame numero, y al terminar se observa que el valor de n ha sido cambiado por el procedimiento. Esto ha ocurrido porque en Qbasic, si no se especifica lo contrario, se pasan los parámetros de los módulos POR REFERENCIA, es decir, si un parámetro es una variable esta podrá ser modificada por el módulo. Para evitar esto, y que los módulos no puedan modificar los valores de las variables que se les pasan como parámetros lo que hay que hacer es escribir en la llamada al módulo la variable entre paréntesis. A esto se llama PASO DE PARÁMETROS POR VALOR y en este caso lo que se pasa es sólo un número, y no la dirección de la variable original que queda "a salvo" del módulo. Veamos un ejemplo: RPrincipal a = 8 b = 5 c = 7 PonACero (a), (b), (c) PRINT a, b, c PonACero (a), b, (c) PRINT a, b, c PonACero a, b, c PRINT a, b, c REste procedimiento asigna 0 a los tres parámetros REntreada: una, otra, yOtra: Reales SUB PonACero(una, otra, yOtra) una=0 otra=0 yOtra=0 END SUB El resultado sería: 8 5 7 8 0 7 0 0 0 Como se puede ver, la primera vez que llamamos al procedimiento pasamos los tres parámetros por valor y no se produce cambio alguno en las variables del programa principal. En la segunda llamada pasamos la variable b por referencia (No lleva
c c
los paréntesis) y su valor es modificado. La última vez pasamos todo por referencia y las tres variables son modificadas y acaban con valor 0. En los ejemplos que hemos puesto los parámetros tienen nombres distintos de las variables del programa principal. Es lo que se suele hacer, ya que deben llevar nombres representativos. Si hubiera coincidido algún nombre no pasa absolutamente nada porque se trata de variables distintas. Lo que no podemos hacer es usar nombres de variables globales ni de otros módulos. En un apartado posterior se hace una comparación con los procedimientos y se dan sugerencias de cuando usar paso de parámetros por referencia y cuando por valor. x x @cc&4"$2"c&c4("$"#c Este apartado se ha quedado para casi el final del tema de módulos porque la única diferencia que hay entre un procedimiento y una función es que un procedimiento no devuelve directamente ningún valor (Aunque puede modificar variables del interfaz), mientras que una función devuelve un valor que es usado por la expresión en la que está insertada la llamada a la función. Para definir funciones usaremos la orden "Nueva Función" del menú "Edición" y escribiremos nuestro código entre las líneas FUNCTION nombreFuncion END FUNCTION Como vimos en el apartado de llamadas a procedimientos y funciones para llamar a un procedimiento escribimos su nombre y sus parámetros al principio de una línea y este hace "lo que sea", mientras que para llamar a una función habrá que poner su nombre en una expresión seguida de los argumentos entre paréntesis, si existen. Para determinar el tipo de dato que devuelve una habrá que ver su nombre. Si no lleva sufijo se entiende que devuelve un valor real de precisión sencilla, si lleva un % devuelve un entero, si lleva un $ devuelve una cadena, etc. Y dentro de la función para indicar el valor a devolver usaremos el nombre de la función seguido del signo Igual y la expresión cuyo valor se devolverá. Veamos unos cuantos ejemplos de funciones con un programa principal que las llama a todas: RPrincipal CLS PRINT "La suma de dos y dos es " ; Suma(2, 2) media = Media3(4, 7, 12) PRINT "La media de 4, 7 y 12 es ", media PRINT "El mayor de 15 o 8 es "; ElMayor(15, 8) IF LaMasLarga("Ronda","Otorrinolaringólogo") = "Ronda" THEN PRINT "RRondaR es más larga que la otra palabra" ELSE PRINT "RRondaR es más corta que la otra palabra"
"c c
ENDIF PRNT NoHaceNada (2, "Palabra") RFunción Suma: Suma dos valores REntrada: n1 y n2: Reales, valores a sumar RSalida: devuelve la suma como real FUNCTION Suma(n1, n2) Suma = n1 + n2 END FUNCTION RFunción Media3: Calcula la media de 3 valores enteros REntrada: n1, n2 y n3: Enteros RSalida: devuelve la media como real FUNCTION Media3(n1, n2, n3) Media3 = (n1 + n2 + n3) / 3 END FUNCTION RFunción Mayor: Devuelve el mayor de dos números REntrada: n1 y n2: Reales, valores a comparar RSalida: devuelve el mayor de los dos como real FUNCTION Mayor(n1, n2) IF n1 > n2 THEN Mayor = n1 ELSE Mayor = N2 END IF END FUNCTION RFunción LaMasLarga$: Devuelve la cadena más larga de las dos REntrada: cad1$ y cad2$: Cadenas de longitud variable RSalida: devuelve la cadena más larga FUNCTION LaMasLarga$(cad1$, cad2$) IF LEN(cad1$) > LEN(cad2$) THEN LaMasLarga$ = cad1$ ELSE LaMasLarga$ = cad2$ END IF END FUNCTION RFunción NoHaceNada$: Devuelve 0 siempre porque no le asignamos nada. R (Los cálculos que hace no tienen ningun efecto) REntrada: n% y cad$: Un entero y una cadena de longitud variable RSalida: Siempre 0 FUNCTION NoHaceNada(n%, cad$) x = 123456 + 78 cadena$ = cad$ + "Hecho en Ronda" x = x / n% END FUNCTION
#c c
Por si hay alguna duda el resultado sería este: La suma de dos y dos es 4 La media de 4, 7 y 12 es 7.666667 El mayor de 15 o 8 es 15 RRondaR es más corta que la otra palabra 0 Ahora veamos algunas cosas importantes que hay en los ejemplos. La llamada a las funciones se puede hacer desde la intrucción PRINT o cualquier otra instrucción, en una expresión que se asignará a una variable, siempre a la derecha del signo igual, o en una expresión que se usa como condición de un IF, WHILE, etc... Para devolver el resultado se asigna una expresión al nombre de la función, en su interior, normalmente al final. Si esto no se llega a hacer nunca, como pasa en la función NoHaceNada, se devuelve cero o una cadena de longitud cero si era una función de tipo cadena. Si se llegan a ejecutar más de una de estas asignaciones el valor que se devuelve es el último. Por supuesto el valor que se asigna tiene que ser numérico o de cadena dependiendo del tipo de datos de la función. En el mismo programa no puede haber dos módulos con el mismo nombre. x x Ccc$(#3&!&c Como hemos visto la programación modular consiste en que unos módulos se llamen a otros para estructurar y dividir el programa, evitar partes repetidas, reutilizar código, etc. El programa principal llama a unos módulos (Ya sean procedimientos o funciones) y estos a su vez pueden llamar a otros módulos. Conforme los módulos terminan de ser ejecutados devuelven el control al que lo llamó que sigue ejecutándose y cuando termina, si era el módulo principal, el programa termina. Llamamos recursividad a la posibilidad de que un módulo se pueda llamar a si mismo. Esto normalmente se hace con funciones que devuelven valores que vamos acumulando, comparando, etc. El principal problema de esta forma de programar es que si una función se llama indefinidamente a si misma las direcciones de retorno de las instrucciones se van acumulando en la memoria del ordenador hasta que no hay espacio suficiente y se produce un error de tipo "Espacio en pila agotado". Una "Pila" es una forma de almacenar datos en la memoria que usa internamente QBasic (Y cualquier otro lenguaje) de forma que se va amontonando información igual que si se tratara de una pila de libros o de platos para poder sacarla en el orden inverso (cogiendo siempre el de arriba), y si esta se hace muy grande ocupará todo el espacio que tiene disponible y provocará este error que no tiene nada que ver con ninguna batería de electricidad ni nada de eso. Para evitar el error lo que se hace siempre es poner alguna condición que si ya no se cumple no seguimos llamando a la función y ya lo que
$c c
se hace es ir volviendo hacia atrás y terminando todas las copias de la función que tenemos "empezadas" hasta que acaba la última y se vuelve al módulo principal y el programa termina. Veamos un ejemplo muy típico de un programa que calcula el factorial de un número usando recursividad. RPrincipal INPUT "Escribe el número: "; n# PRINT "El factorial de "; n#; " es "; Factorial#(n#) RFACTORIAL: Calcula el factorial de forma recursiva REntrada: Real de doble precisión: Número a calcular RSalida: El factorial como real de doble precisión FUNCTION Factorial#(n#) IF n# = 1 THEN Factorial# = 1 ELSE Factorial# = n# * Factorial#(n# - 1) END FUNCTION Ahora veamos el planteamiento del problema. El factorial de un número es el producto de todos los números desde 1 hasta el número, por ejemplo el factorial de 4 es 4 * 3 * 2 * 1, es decir, 24. Para calcularlo de forma recursiva hemos definido el factorial de un número como el producto de dicho número por el factorial del "número anterior". Para salir de la recursividad ponemos como condición que si el número es 1 el factorial ya es 1 y no tenemos que seguir llamando a la función. Hemos definido tanto la función como las variables de tipo real de doble precisión (poniéndole el & como sufijo) porque es el tipo de datos de QBasic que admite valores más altos ya que el factorial puede alcanzar valores muy elevados. Veamos lo que ocurriría paso a paso suponiendo que el usuario quiere calcular el factorial de 4. *c El programa principal llama a factorial y le pasa 4. *c Dentro de factorial, 4 es distinto de 1 y se calcula como 4 por factorial de 3. *c Dentro de la segunda copia de factorial, 3 es distinto de 1 y se calcula como 3 por factorial de 2. *c Dentro de la tercera copia de factorial, 2 es distinto de 1 y se calcula como 2 por factorial de 1. *c Dentro de la cuarta copia de factorial, 1 es igual a 1 y se calcula factorial como 1 directamente. Esta copia de la función termina su ejecución y devuelve 1. *c La tercera copia de factorial toma el valor 1 devuelto por la cuarta, calcula el factorial 2 * 1 = 2 y termina devolviendo este valor. *c La segunda copia de factorial toma el valor 2 devuelto por la tercera, calcula el factorial 3 * 2 = 6 y termina devolviendo este valor.
"c c
La primera copia de factorial toma el valor 6 devuelto por la segunda, calcula el factorial 4 * 6 = 24 y termina devolviendo este valor. *c El programa principal toma este valor 24 que ya es el definitivo, lo escribe en pantalla y termina. Como se ha podido ver conforme vamos entrando en las funciones se van llamando a si mismas sucesivamente hasta el punto en que una de ellas ya no lo hace más y termina devolviendo un valor a la anterior que lo va acumulando y termina. Así todas se van cerrando hasta que llegamos al programa principal que se queda con el último valor y termina el programa. Si hubiera habido alguna variable local es muy importante comprender que aunque haya muchas copias de la función y todas tienen el mismo nombre se trata de variables totalmente distintas, cada una de su copia de la función, que no compartirán sus valores en ningún caso y en todo caso estarían ocupando memoria por separado. Podemos comprobar que si intentamos calcular con este programa el factorial de un número superior a aproximadamente 30 se produce el error de "Espacio en pila agotado" ya que tenemos en memoria a la vez 30 copias de la función con sus variables de entorno y sus punteros a las funciones que los han llamado. En un programa no recursivo casi nunca llegaremos a tener funciones "anidadas tan profundamente" y no se producirá este error. Como se ha podido ver la programación recursiva es algo difícil de entender y que puede llegar a producir errores (En otros lenguajes se bloquearía el ordenador al desbordarse la pila). Este problema se podía haber resuelto con un bloque FOR de una forma tan sencilla como esta: factorial& = 1 FOR i = 1 TO n factorial& = factorial& * n NEXT Todos los problemas se pueden resolver siempre de la forma normal con condiciones y bucles (Esquema iterativo) o usando módulos que se llaman a si mismo (Esquema recursivo). Solo deberemos utilizar la recursividad cuando demostremos en la fase de análisis del programa que el algoritmo recursivo es más rápido o más corto que el iterativo. En la mayoría de problemas sencillos normales y con las distintas posibilidades de usar bucles REPETIR y MIENTRAS que ofrece QBasic y los otros lenguajes estructurados es aconsejable evitar la recursividad siempre que se pueda ya que puede ser difícil demostrar matemáticamente que un procedimiento complicado usado de forma recursiva no va a llegar a ejecutarse demasiadas veces desbordando la pila y llegando a detener la ejecución del programa. *c
" c c
x x x;cc&4"$!c"%c$&"%#c7c4("$"#c En la programación modular hemos diferenciado dos tipo de módulos: Las funciones, que devuelven un valor tras su ejecución y los procedimientos que no devuelven ningún valor. En el lenguaje de QBasic la forma de llamar a uno y a otro es distinta pero en otros lenguajes como C todos los módulos son iguales y es el usuario el que tiene que determinar si usa al valor devuelto por los módulos insertando su llamada en una expresión, o no. Queda claro que tendremos que usar una función cuando queramos obtener un valor y tendremos que usar un procedimiento cuando solo queramos que se haga algo o bien queramos estructurar el programa dividiéndolo en varias partes que serán llamadas cuando haga falta por un menú situado en el módulo principal. Nos puede ocurrir que necesitemos que un módulo nos devuelva dos o más valores del mismo o de distinto tipo. En este caso no podemos usar una función porque esta sólo devuelve un valor. Habrá que usar un procedimiento al que le pasaremos parámetros por referencia y nos los devolverá modificados cuando termine. Veamos un ejemplo muy sencillo de un procedimiento que intercambia el valor de dos variables: RPrincipal a = 92 b = 7 PRINT "a vale "; a; " y b vale "; b Intercambia a, b PRINT "a vale "; a; " y b vale "; b RProcedimiento Intercambia RIntercambia los valores de dos variables numéricas REntrada: a y b: Reales cuyos valores serán intercambiados SUB Intercambia(a, b) aux = a a = b b = aux END SUB Donde el resultado sería: a vale 92 y b vale 7 a vale 7 y b vale 92 El programa es muy fácil de entender. El procedimiento modifica las dos variables que se le han pasado por referencia. En este caso las dos variables son del mismo tipo, pero en otro problema podían haber sido de cualquiera y se modificarían igual. Por ultimo aclarar dos conceptos importantes en la teoría de la programación modular: Llamamos "Funciones Puras" a las que tienen estas características: *c Solo comparte datos con el resto del programa a través de sus variables de interfaz, es decir, sus parámetros. *c Todos los parámetros se pasan por valor.
"c c
No pide o muestra información al usuario ni a otros dispositivos del sistema (Discos, impresoras, etc...). *c No usa tipos de datos definidos por el usuario en el programa principal. (Esto veremos lo que es en próximos temas) *c No llama a funciones ni procedimientos que no sean "puros". De esta forma conseguimos que al llamar a una función con unos determinados parámetros siempre nos devuelva el mismo valor, y también conseguimos poderla usar en otro programa de QBasic solo con copiarla y pegarla sin tener que modificar ni añadir nada estando completamente seguros de que su funcionamiento va a ser siempre idéntico. Llamamos "procedimientos puros" a los que tienen estas características: *c Solo comparte datos con el resto del programa a través de sus variables de interfaz, es decir, sus parámetros. *c Todos los parámetros se pasan por referencia. *c Puede compartir información con el usuario y con dispositivos externos. En algunos casos, especialmente en las funciones tendremos que intentar que sean puras, y en todos los programas será conveniente no abusar de las variables globales ya que pueden hacer que los errores lógicos sean difíciles de encontrar, además de que si usamos un módulo en otro programa será necesario que existan las mismas variables globales para que todo funcione bien, cosa que puede ser un problema. *c
x x xxcc%%#c Si has guardado algún programa de los que incluyen procedimientos o funciones habrás observado que al hacerlo aparecen automáticamente al principio del listado, en el módulo principal, unas líneas que empiezan con DECLARE SUB o DECLARE FUNCTION seguida de los nombres y los parámetros de los módulos. Estas líneas son lo que se conoce en programación como c de procedimientos y funciones. No tienen nada que ver con los prototipos de un nuevo invento que está en desarrollo ni nada de eso, sirven para indicarle a QBasic de que tipo son los parámetros y los datos que devuelven los módulos para que los "conozca" antes de ser llamados y no tenga que ir a buscarlos por todo el código fuente del programa cada vez que hagan falta. Estas líneas las escribe automáticamente QBasic al guardar los programas y nosotros no tenemos que molestarnos en escribirlas ni conocer su sintaxis correcta. Si nuestro programa no tiene los prototipos, ya sea porque los hemos borrado o porque todavía no hemos guardado el programa y no se han creado, todo funciona perfectamente pero se supone que las llamadas a módulos van algo más lento (no lo he cronometrado). Si borramos los prototipos volverán a aparecer la próxima vez que guardemos el programa.
"c c
Los prototipos, si existen, siempre van a ser la primera línea del programa, antes no puede haber ninguna instrucción ejecutable. Si movemos los prototipos hacia abajo o escribimos alguna instrucción antes se generará un error y tendremos que volver a dejarlos al principio. Lo que sí podemos escribir antes de los prototipos al principio del programa son líneas de comentario (Que empiezan con un apóstrofo). Es común en QBasic, cuando tenemos un programa terminado más o menos largo, escribir al principio veintitantas líneas de comentario que incluyen un título bonito y alguna descripción para que ocupen la primera pantalla del listado y sea lo primero que se ve en el editor al abrir el programa. Si ya tenemos nuestros módulos, hemos guardado el programa y se han generado los prototipos, y después hacemos algún cambio en los parámetros de alguno de ellos es posible que la cabecera del módulo no se corresponda con su prototipo y al ejecutar el programa se produzca algún error. En este caso la forma más cómoda y más rápida de solucionar el problema es borrando todos los prototipos y guardando el programa para que se generen de nuevo correctamente, mejor que intentar arreglarlos nosotros mismos. También puede ser importante saber que un prototipo no se generará si todavía no llamamos al módulo desde ninguna otra parte del programa, aunque el código ya esté escrito.
| c c| c c|c c c c c x x xcc&J-cc.ccc x x cc( c : -c c c -c c c D c x x xcc&4"$2"c&c"(3#c%#c&c&!%#c Hasta ahora en una variable podíamos almacenar solo un dato (Un número entero o real, una cadena, etc...) o podíamos usar arrays para manejar gran cantidad de datos, pero siempre se trataría de una colección de datos del mismo tipo. En este tema vamos a ver cómo definir tipos de datos que contengan más de un valor que puede ser de tipos distintos a la vez. Imaginemos una agenda donde necesitamos manejar nombre dirección, teléfono, y edad de las personas. Vemos que los tres primeros datos son cadenas, aunque el teléfono será siempre más corto que los otros dos, y la edad es un entero. Con lo que hemos visto hasta ahora tendríamos que usar para cada individuo cuatro variables distintas que manejaremos por separado, ya sean variables simples o vectores con subíndices para muchas personas a la vez. Veamos un programa muy sencillo que pide los datos de dos personas, los almacena en estas nuevas variables que vamos a ver y finalmente presenta en el nombre de la que sea más joven. Que nadie se asuste, después explicaremos lo que es cada cosa. *c *c
TYPE TIPOPERSONA
"c c
nombre AS STRING * 30 direccion AS STRING * 40 telefono AS STRING * 9 edad AS INTEGER END TYPE DIM persona1 AS TIPOPERSONA DIM persona2 AS TIPOPERSONA CLS INPUT "Nombre de la primera persona: ", persona1.nombre INPUT "Dirección de la primera persona: ", persona1.direccion INPUT "Teléfono de la primera persona: ", persona1.telefono INPUT "Edad de la primera persona: ", persona1.edad PRINT INPUT "Nombre de la segunda persona: ", persona2.nombre INPUT "Dirección de la segunda persona: ", persona2.direccion INPUT "Teléfono de la segunda persona: ", persona2.telefono INPUT "Edad de la segunda persona: ", persona2.edad PRINT IF persona1.edad < persona2.edad THEN PRINT "El más joven es "; persona1.nombre) ELSE PRINT "El más joven es "; persona2.nombre) END IF El resultado sería: Nombre de la primera persona: Carlos Rodríguez Dirección de la primera persona: Carrera Espinel nº 172 6ºA Teléfono de la primera persona: 952870000 Edad de la primera persona: 23 Nombre de la segunda persona: Fernando Garcia Dirección de la segunda persona: Plaza del Ahorro nº 21 4ºC Teléfono de la segunda persona: 952879999 Edad de la segunda persona: 19 El más joven es Fernando García Si hubiéramos usado variables simples no tendría que haber ningún problema ¿No?, pero como hemos usado un tipo definido por el usuario vamos a verlo todo paso a paso. Lo primero que hacemos de todo es declarar que vamos a usar un nuevo tipo de datos que se va a llamar TIPOPERSONA y va a estar formado por cuatro miembros que ahora declaramos de forma explícita: *c Nombre va a ser de tipo cadena con un máximo de 30 caracteres.
"c c
Dirección va a ser de tipo cadena con un máximo de 30 caracteres. *c Teléfono va a ser de tipo cadena con un máximo de 9 caracteres. *c Edad va a ser un entero. Y ya hemos terminado de declarar TIPOPERSONA A continuación declaramos dos variables, persona1 y persona2 como de tipo TIPOPERSONA en vez de real o entero o algo así. Después pedimos al usuario los datos de la primera persona y los vamos guardando cada uno en el lugar correspondiente de la variable persona1 usando el nombre de la variable seguido de un punto y del nombre del miembro al que nos estamos refiriendo en cada caso. El tipo de datos que podemos guardar en cada sitio depende del tipo de dato del miembro, por ejemplo en .edad no podemos guardar una palabra. Repetimos lo mismo, pero esta vez almacenando la información en persona2. Finalmente hacemos una comparación entre las dos edades y escribimos el nombre de la persona más joven. Como se puede ver no es tan difícil. Lo que hemos hecho es agrupar cuatro variables independientes bajo un nombre común, pero de todas formas tendremos que manejar cada una por separado usando el nombre de la variable que hayamos declarado de este tipo seguido del punto y del nombre del miembro. Es muy importante entender que TIPOPERSONA no es una variable, sino un tipo de datos que hemos definido en nuestro programa y se une a los tipos STRING, INTEGER, LONG, SINGLE y DOUBLE de que ya dispone QBasic y que para poderlo utilizar hay que declarar explícitamente que una variable sea de este tipo usando la instrucción DIM y que ese nombre que le demos a la variable será el que hay que utilizar en el código del programa. Si usamos el nombre de la variable sin el punto ni el nombre de uno de sus componentes miembro se producirá un error de "Tipos no coinciden" porque QBasic no sabe a cuál de los miembros nos estamos refiriendo. Si escribimos mal el nombre del componente se producirá el error "Elemento no definido". Si escribimos mal el nombre de la variable ocurrirá que QBasic se creerá que estamos usando una nueva variable y aparentemente no ocurrirá nada, pero se pueden producir errores de lógica muy difíciles de detectar, problema común en QBasic por no exigir la declaración de variables. Los componentes miembro pueden ser de cualquiera de los cinco tipos de que dispone QBasic, solo que en el caso de que se declaren como cadenas tendrán que ser de longitud fija. Habrá que poner la palabra STRING seguida de un asterisco (*) y del número de caracteres que va a tener la cadena. Como vimos en el tema de tipos de datos si intentamos asignar una cadena de tamaño superior al máximo no ocurre ningún error, pero todo lo que sobre se pierde. *c
"c c
También podemos declarar algún miembro como de un tipo de datos ya definido por el usuario anteriormente en el mismo programa. Veamos un ejemplo: TYPE TIPONOMBRE nombre AS STRING * 15 apellido1 AS STRING * 15 apellido2 AS STRING * 15 END TYPE TYPE TIPODIRECCION calle AS STRING * 30 numero AS INTEGER piso AS INTEGER puerta AS STRING * 1 cp AS STRING * 5 ciudad AS STRING * 20 provincia AS STRING * 2 END TYPE TYPE TIPOPERSONA nombre AS TIPONOMBRE direccion AS TIPODIRECCION telefono AS STRING * 9 edad AS INTEGER END TYPE Si declaramos una variable de TIPOPERSONA haciendo: DIM persona AS TIPOPERSONA Para acceder a todos sus componentes escribiríamos cosas como: persona.nombre.nombre persona.nombre.apellido1 persona.direccion.calle persona.direccion.ciudad persona.edad De esta forma podemos estructurar muy bien datos que tengan una estructura compleja utilizando una jerarquía de variables que puede resultar muy descriptiva, pero tampoco conviene pasarse. Normalmente no será necesario usar más de dos o tres niveles de datos definidos por el usuario porque si lo hacemos los identificadores de las variables pueden llegar a ser muy largos. Como se puede ver los miembros de distintos tipos de datos pueden llevar nombres iguales. Como siempre vamos a usar estos nombres junto con el nombre de la variable declarada que sí va a ser distinto en cada caso no habrá ninguna confusión. También es muy útil poder declarar arrays de un tipo definido por el usuario. Por ejemplo si usamos el TIPOPERSONA del primer ejemplo podemos declarar un vector de 50 personas haciendo: DIM personas (1 TO 50) AS TIPOPERSONA Y para acceder a los elementos del vector haríamos cosas como... personas(3).nombre personas(17).edad personas(40).telefono
""c c
...igual que si se tratara de vectores de un tipo normal, solo que habrá que especificar cual miembro queremos usar. Es posible declarar variables y módulos con el mismo nombre que un tipo de datos definido por el usuario, pero para evitar confusiones es recomendable evitar esto a toda costa. Podemos definir tipos de datos dentro de procedimientos o funciones, pero si lo hacemos sólo estarán disponibles para las variables que declaremos dentro del mismo módulo. Habrá que definirlos después de la línea SUB nombre o FUNCTION nombre. Los tipos definidos en el módulo principal estarán disponibles para todos los módulos del programa, pero si después queremos exportar módulos que los usen a otros programas también habrá que copiar la definición del tipo de dato para que todo funcione correctamente. Para la construcción de los nombres de los tipos de datos se siguen las mismas normas que para los nombres de variables y módulos. Normalmente se escribe todo en mayúsculas para que resalte más y se empieza siempre por TIPO para darse cuenta fácilmente de que no se trata de una variable o un módulo. El uso intensivo de tipos de datos definidos por el usuario junto con las constantes ayudan a que el código del programa quede muy detallado y sea más fácil de leer. x x cc("!c!8!$2"c!c'!c !!$2"c "%!&!c!c 5%#c Antes de nada aclarar que QBasic no es un lenguaje de programación orientado a objetos. En el apartado anterior hemos visto como ejemplo una estructura a la que llamábamos TIPOPERSONA que tenía unos datos miembros como son nombre, edad, etc... y que nos servía como "molde" para crear una o muchas variables que tienen todas estas características. Es fácil pensar en los tipos de datos como la definición de una CLASE de objetos que agrupa a una serie de DATOS MIEMBROS que van a tener todas las INSTANCIAS de las variables que se declaren de esa clase. Esto puede ser la base de la Programación Orientada a objetos. Una nueva metodología de programación que crea lo que se llama "Tipos de datos abstractos" que además de datos miembros incluyen procedimientos miembros (Métodos) y se utiliza de forma que puede reutilizar datos de otras clases ya existentes creando lo que se conoce como "Jerarquía de clases". Estas clases serán usadas en los programas para definir Objetos de esa clase que tendrán todas estas características y podrán ser utilizados por los programas llamando a sus métodos. Estas técnicas se utilizan ampliamente en lenguajes de programación mucho más avanzados como C++, Object Pascal o Java donde los programadores pueden usar objetos ya existentes o además crear los suyos propios definiendo estas clases. Si ha quedado claro el tema de tipos de datos definidos por el usuario en QBasic y los manejamos
"#c c
con soltura puede ser que nos resulte un poco más sencillo (o menos difícil) aprender a programar objetos en otros lenguajes más modernos de los que se utilizan en la actualidad, aunque de todas formas habrá que aprender toda una multitud de conceptos nuevos y técnicas de programación que no existen en QBasic.
c | c c! c c *cx *cx *cx
x/ xcc-c cccJ x/ cc-c ccJc x/ cccJ -ccJc *cx x/ cc'cJ -ccJc *cx x/ /cc!: cJ -c cJc
c x x/ xcc"%&($$2"c!'c(#c&c4$)#c Hasta ahora en nuestros programas para almacenar información hemos utilizado variables que son la forma que tienen los lenguajes de programación de almacenar datos en la memoria RAM del ordenador. Es una forma muy cómoda y extremadamente rápida de manejar datos, pero tiene el grave inconveniente de que toda la información se pierde al finalizar la ejecución del programa. En el tema de los arrays vimos varios ejemplos de programas donde se pedían los nombres de las personas que viven en un bloque de pisos. Todo funcionaba muy bien pero había que hartarse de escribir todos estos nombres cada vez que entrábamos al programa porque al terminar se pierden. Una solución para evitar esto podría ser incluir los nombres de las personas en el código fuente del programa, pero estos nombres pueden cambiar alguna vez y el usuario del programa no tiene por qué saber cómo modificar esto. La solución definitiva a este problema consiste en guardar la información en el sistema de archivos de las unidades de disco del ordenador donde permanecerá por tiempo indefinido y podremos recuperarla o modificarla todas las veces que queramos. La información se almacena en los discos del ordenador en unas estructuras lógicas llamadas ficheros o archivos. Mientras que en una cinta de vídeo si grabamos una película nosotros nos tenemos que preocupar de ver dónde está el final para grabar lo siguiente a continuación y no borrar un trozo de lo que ya tenemos, en el ordenador es el sistema operativo (MS-DOS, Linux, Etc.) el que se encarga de controlar en que parte del disco se graban los datos y de ponerle un nombre y situarlo en el sistema de archivos (La FAT y los directorios) para que nosotros podamos acceder a esa información solo con dar el nombre del archivo sin tener que preocuparnos de en que parte del disco está o de si nos vamos a cargar otra información al grabar nuevos datos. Principalmente hay dos tipos de ficheros: Secuenciales y de acceso aleatorio. Aunque están grabados en los discos de idéntica forma se
"$c c
diferencian de cómo los manejamos nosotros desde nuestro programa. En los siguientes apartados veremos las diferencias y la forma de usar cada uno de ellos. Ficheros secuenciales son aquellos en que los datos se graban o se leen línea a línea siempre empezando por el principio. Normalmente al abrirlo se copiará su contenido en alguna estructura de variables de nuestro programa para su utilización y posible modificación. Después si hay que guardarlo se hará completamente sustituyendo a toda la información que hubiera anteriormente en el fichero, aunque alguna parte de la información vuelva a ser la misma. El concepto es similar a lo que hacemos con los ficheros de las aplicaciones normales como procesadores de texto o los propios ficheros *.BAS de los programas de QBasic. Ficheros de acceso directo o acceso aleatorio (No tiene nada que ver con los iconos de acceso directo de Windows) son los que nos permiten leer o modificar un determinado dato sabiendo su posición sin tener que recorrer todos los datos anteriores. El ejemplo más típico de estos ficheros son las tablas de las bases de datos donde podemos añadir, modificar, consultar o borrar la información de una persona (un registro) sin preocuparnos de los demás. La extensión que elijamos para nuestros ficheros puede ser cualquiera, pero deberemos evitar usar extensiones ya usadas por otros programas que podrían provocar conflictos en Windows. Lo más normal es usar extensiones como .TXT para los ficheros secuenciales que solo contienen texto y .DAT en general para los que pueden contener texto o cualquier otro tipo de caracteres. c x x/ cc"%&($$2"c!c'#c4$)#c#$("$!'#c La forma más elemental de trabajar con ficheros es leerlos línea por línea, una tras otra, para irlos analizando o irlos guardando en una estructura de datos de nuestro programa. Si queremos guardarlo se hará de la misma forma, escribiendo líneas una tras otra desde la primera hasta la última. Los ficheros secuenciales normalmente se leen siempre enteros y para guardarlos se puede elegir entre reemplazar completamente su contenido o bien guardar las nuevas líneas a continuación de las existentes, a lo que llamaremos "Anexar". Entendemos por "Línea" a una secuencia de caracteres alfanuméricos (Letras, números y signos de puntuación) terminados por el carácter especial de retorno de carro que es el que se genera cuando pulsamos la tecla Enter. Este carácter no lo vemos en la pantalla pero se genera automáticamente al final de lo que escribimos con la orden PRINT, si no ponemos un punto y coma al final, como vimos en el tema de Entrada y Salida. Para trabajar con ficheros hay que "Abrirlos" especificando su nombre y si queremos leer o escribir en él. A continuación leeremos o escribiremos los datos, según corresponda por el modo en que está abierto, y finalmente lo cerraremos.
#c c
x x/ cc#$ c"4!$2"c"c4$)#c #$("$!'#c Vamos con un ejemplo de un programa que abre un fichero para escritura (lo crea), escribe en él los nombres de los días de la semana y finalmente lo cierra. "cT :Tc4c(%(%c!#cBxc "%cBx0cT'Tc "%cBx0cT Tc "%cBx0cTLTc "%cBx0cT5.Tc "%cBx0cT3Tc "%cBx0cT# Tc "%cBx0cT& Tc $'#cBxc Este programa no produce ningún resultado en la pantalla, sólo el rótulo "Presione cualquier tecla y continúe" para indicar que ha terminado. Si buscamos el fichero diasem.txt y lo abrimos con el bloc de notas de Windows comprobaremos que contiene lo siguiente: 'c c c 5.c 3c # c & c Salvo los típicos problemas con los acentos, es lo que esperábamos. Si lo abrimos con el EDIT de MS-DOS veremos los acentos correctamente. Ahora vamos a comentar detalladamente cómo funciona el programa. La primera línea es la más importante cuando trabajamos con ficheros. Si la traducimos al castellano dice algo así como: !cT :Tc c cc cIc cBxc Visto esto ya está claro su significado pero de todas formas hay que decir un montón de cosas: *c Al abrir un fichero para escritura si no existe se crea y si existe se borra su contenido para empezar desde cero. *c Si no especificamos una ruta de directorios, como hemos hecho en este caso, el fichero se crea en el "directorio activo". El directorio activo es en el que nos encontrábamos al entrar a QBasic desde MS DOS. En el case de que hayamos entrado desde Windows será el que aparece en la casilla "Iniciar en:" de las propiedades del acceso directo a QBasic. Para cambiar el directorio activo desde QBasic podemos usar la orden CHDIR seguida de la nueva ruta de directorios. Esta orden la veremos con más detalle en el tema dedicado a comunicación con aplicaciones externas.
# c c
Si especificamos una ruta tendrá que existir o se producirá un error. Al crear un nuevo archivo no se crean directorios que no existan. *c El nombre y ruta del archivo a abrir se pueden especificar de forma literal, como hemos hecho aquí, o bien usar una expresión de cadenas cuyo valor sea la ruta y el nombre del archivo con la sintaxis correcta (Barras inversas, puntos y demás). La única cosa que nos puede resultar extraña de la instrucción OPEN es ese #1 que aparece al final. A este número le llamamos "descriptor de archivo" y nos va a servir para identificar unívocamente al archivo que acabamos de abrir en las órdenes de entrada y salida de nuestro programa hasta que cerremos el archivo. Por lo tanto si abrimos otro archivo antes de cerrar este tendremos que usar el descriptor #2 y así sucesivamente. QBasic nos permite tener abiertos a la vez hasta 255 archivos, pero la memoria disponible y la cláusula FILES del archivo CONFIG.SYS nos limitarán el número. Normalmente no necesitaremos más de uno o dos archivos abiertos a la vez en nuestros programas sencillos. Siguiendo con el código de nuestro programa a continuación tenemos una serie de instrucciones PRINT que como llevan el descriptor de un archivo escriben en el archivo en lugar de en la pantalla. Podemos usarlas exactamente de la misma forma que cuando las usamos normalmente para escribir en pantalla. No tienen por qué estar todas juntas. Finalmente lo que hacemos es cerrar el archivo con la orden CLOSE. Conforme vamos escribiendo en el archivo con la orden PRINT la información no se graba en el disco inmediatamente, sino que se va almacenando de forma transparente para el usuario en un área especial de la memoria (El "Buffer" de escritura en disco) o bien en registros de memoria especial dentro de la placa de circuitos del propio disco duro y cuando hay información suficiente es cuando el cabezal del disco se mueve y la información se graba físicamente en el disco. Al cerrar el archivo nos aseguramos de que la información que quedara por escribir se grabe realmente en el disco, y que además todos los recursos utilizados, así como el número del descriptor se liberen y queden disponibles para ser usados por otro archivo. Si programamos para un sistema multitarea (Windows 9x lo es, más o menos) es muy importante ocupar los recursos del sistema el menor tiempo posible y por lo tanto deberemos cerrar los archivos que ya no nos hagan falta abiertos aunque todavía vayamos a seguir utilizando otras partes del programa durante más tiempo. Cuando abrimos un fichero este queda disponible para todos los módulos (Programa principal, procedimientos y funciones) de nuestro programa, con lo que podemos abrirlo desde un módulo, leer o escribir desde otro, y cerrarlo desde otro, pero siempre teniendo en cuenta que solo lo podemos usar después de haberlo abierto y antes de cerrarlo.
*c
#c c
x x/ cc'c"4!$2"c&c4$)#c#$("$!'#c En el apartado anterior hemos abierto un archivo para escritura. Ahora vamos a abrir uno existente para leer su contenido. Veamos un ejemplo de un programa que abre el fichero ya existente "diasem.txt" que hemos creado con el programa anterior para mostrarlo en pantalla. $'#c "cT :Tc4c"(%c!#cBxc X)'c"%c4ExGc c '"c"(%cBx0c 9c c "%c 9c X"&c $'#cBxc Este programa no produce ningún cambio en el archivo y su resultado por pantalla es: 'c c Lc 5.c 3c # c & c Ahora veamos cómo funciona. *c Borramos la pantalla. *c Abrimos el archivo "diasem.txt" para lectora (INPUT) y le llamamos #1. *c Mientras no se acabe el archivo 1, leemos línea por línea y las vamos escribiendo en pantalla usando la variable linea$. *c Cerramos el archivo. Como se puede ver, la instrucción OPEN es igual a la anterior, sólo varía el modo de apertura que en este caso es INPUT, es decir, entrada de datos, lectura. Si el fichero especificado no existe se producirá un error de tiempo de ejecución. En el apartado de control de errores veremos cómo detectar si un fichero existe antes de inventar abrirlo. A continuación vamos leyendo las líneas del archivo. Como no tenemos por qué saber cuantas líneas tiene el archivo, usaremos un bucle MIENTRAS que usa como condición la función EOF (End Of File) que devuelve Verdadero si se ha alcanzado el final del archivo y falso en caso contrario. Observa que a EOF le pasamos como parámetro el descriptor del archivo, pero sin el carácter #. Esto es para poder usar alguna expresión matemática que devuelva el número del archivo si es que tenemos varios abiertos y no queremos usar el número de forma literal. Dentro del bucle lo que hacemos es leer una línea usando la instrucción LINE INPUT seguida del descriptor del archivo y del nombre de la variable donde la queremos guardar. Esta instrucción permite leer líneas de hasta 255 caracteres que acaben con un carácter especial de retorno de carro (Las que en el ejemplo
#c c
anterior escribimos con la orden PRINT). Seguidamente escribimos en la pantalla el valor de la variable line$ usando la orden PRINT como hemos hecho siempre. Finalmente, terminado el bucle, cerramos el archivo para dejar el recurso disponible. Como estaba abierto para lectura no se producirá ningún cambio. Es muy importante entender que para leer dados de un fichero secuencial debemos usar siempre un bucle MIENTRAS, y no un REPETIR, ya que en este caso intentaríamos leer al menos una línea del fichero antes de comprobar si se ha llegado al final, y se puede dar el caso de que un fichero exista, pero esté completamente vacío con lo que intentaríamos leer más allá del final del final del archivo y generaríamos un error de tiempo de ejecución. En la ayuda de QBasic viene un ejemplo hecho con un bloque DO...LOOP y se puede comprobar que puede que no funcione correctamente en el caso de que el fichero esté vacío. También podemos decir aquí que si somos nosotros quienes escribimos el archivo a leer usando un editor de texto es conveniente pulsar al final la tecla ENTER una vez para que el cursor pase a la línea de abajo y se genere el carácter de retorno de carro. Si no lo hacemos puede ocurrir que cuando leamos el archivo con nuestro programa de QBasic en algunos casos no se llegue a leer la última línea. Esta indicación también la hace MS-DOS para los ficheros CONFIG.SYS, AUTOEXEC.BAT y los ficheros de proceso pro lotes que nosotros podamos escribir. Para terminar con la teoría de los ficheros secuenciales vamos a ver un ejemplo de un programa que copia un fichero de texto en otro poniéndole todo el texto en mayúsculas con ayuda de la función UCASE$. El usuario tendrá que escribir los nombres de los dos ficheros, el existente y el nuevo, y el programa los abrirá los dos a la vez, uno para lectura y el otro para escritura. $'#c "(%cT"ccJcc cI c:UcT0cJx9c "(%cT"cc.cJUcT0cJ9c "cJx9c4c"(%c!#cBxc "cJ9c4c(%(%c!#cBc "%cT'IcIc 0ccJ .c Tc X)'c"%c4cExGc c '"c"(%cBx0c 9c c "%cB0c($!#9E 9Gc X"&c $'#c "%cTc Tc c El resultado por pantalla podría ser: "ccJcc cI c:Uc :c "cc.cJUc :c 'IcIc 0ccJ .c c
#c c
c c Y en los ficheros el que pusiéramos primero no se modificará, mientras que el segundo si no existe se crea y si existe se sobrescribe con el contenido del primero en mayúsculas. Ahora veamos algunas cosas del programa: *c Como se puede ver, tenemos dos ficheros abiertos a la vez y los identificamos con un descriptor diferente. *c Leemos datos mientras no se acabe el fichero 1 y los vamos escribiendo al fichero 2 convertidos a mayúsculas usando la función UCASE$. *c Sacamos un mensaje por pantalla antes de empezar y después de terminar para que el usuario vea "que ha pasado algo". Si prevemos que el proceso va a ser muy largo tendríamos que sacar algún mensaje de progreso cada vez que se haga un número de pasadas del bucle. Para hacer esto usaríamos algún contador. *c Finalmente cerramos todos los ficheros abiertos usando la orden CLOSE sin pasarle ningún descriptor. Si escribes el programa en QBasic y lo ejecutas podrás comprobar que puede ser difícil acertar a la primera con el nombre del archivo, y más si ponemos una ruta de directorios, unidades de disco, etc... con lo que se producirá un error de ejecución. También si se te ocurre poner el mismo nombre las dos veces nos dará un error de tiempo de ejecución diciendo que el fichero ya está abierto. En el tema de manejo de errores veremos cómo se pueden solucionar estos problemas. x x/ /cc!"8!c"4!$2"c!c4$)#c#$("$!'#c Cuando abríamos un fichero para escritura usando el modo OUTPUT decíamos que si no existe se crea y si existe se borra su contenido, pero en algunos caso nos puede interesar que el contenido del fichero no se borre y las nuevas líneas de información se añadan al final del fichero ya existente, incrementando su tamaño. Es el caso de los ficheros .LOG que utilizan muchos programas para llevar un registro de acciones o de problemas que puedan surgir y en los que los distintos procesos o programas van añadiendo información conforme va haciendo falta. La forma de abrirlo es usando el modo APPEND en la instrucción OPEN, por ejemplo: "cT. :Tc4c!"&c cBxc En este caso si no existe el fichero eventos.txt se crea, y si existe se conserva, añadiéndose la nueva información al final. La forma de trabajar con un fichero abierto en modo APPEND es exactamente la misma que en uno abierto en modo OUTPUT. Hay que recordar que no podemos asegurar que la información se graba físicamente en el fichero hasta que no lo cerremos con la orden CLOSE. El tamaño de los ficheros usados sólo de esta forma siempre va a crecer y seremos nosotros los encargados de observar que no se hace
#c c
demasiado grande y de borrarlo cuando ya no nos haga falta la información que contiene. c
| c c! c c c |cc | c x x= xcc-c ccJc x x= cc' cIc ccJc x x= cc ccJc *c x x= xcc-c c ccJ *c x x= cc! c!H c *c x x= cc$ c ? c cc *c x x= ccJ cc *c x x= /cc D c c *c x x= =cc$ -ccJc *c x x=
*c *c *c
x x= xcc"%&($$2"c!c'#c4$)#c&$%#c 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 qué 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
#c c
programa lo calcule automáticamente imitando a los tipos de datos auto numéricos de las bases de datos. c x x= cc'$%(!c7c#$%(!c"c4$)#c&$%#c 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 cómo leer los datos del archivo, ahora vamos a ver cómo funciona este programa. *c Lo primero que hacemos es definir un tipo de datos personalizado que tiene dos miembros, nombre de 20 caracteres y edad que es un entero. *c A continuación declaramos una variable como del tipo de datos que acabamos de definir y que nos va a servir como "Plantilla" para trabajar con nuestro archivo de acceso aleatorio. *c Después borramos la pantalla como siempre.
#"c c
Ahora abrimos el archivo "agenda.dat" en modo de acceso aleatorio (RANDOM), le asignamos el descriptor #1 y con la cláusula LEN le decimos cuanto largo van a ser los registros. Podíamos haberlo calculado nosotros viendo cuando ocupan todos los miembros de nuestro tipo de datos y poner simplemente el número de bytes, pero es más cómodo y más seguro que lo haga la función LEN (Se llama igual que la cláusula, pero cada cosa es diferente). *c Seguidamente ponemos un contador (n) a cero. En este caso en QBasic no hubiera sido necesario, pero es mejor acostumbrarse a hacerlo siempre. *c A continuación entramos en un bloque REPETIR, incrementamos en 1 el contador y le pedimos al usuario que escriba el nombre de la persona y después la edad y lo guardamos cada cosa en su sitio en la variable persona. *c Ahora vamos a escribir en el fichero de acceso aleatorio el registro completo, donde ya tenemos almacenado el nombre y la edad que acaba de escribir el usuario. Para hacer esto usamos la instrucción PUT a la que le tenemos que pasar como parámetro el descriptor del archivo (el carácter # es opcional), el número de registro al que nos estamos refiriendo, para lo que usaremos el contador n, y los datos propiamente dichos que están almacenados en la variable persona. *c Preguntamos al usuario si quiere meter más datos y si pulsa la N mayúscula salimos del bucle, en caso contrario volvemos al principio donde incrementamos el contador, volvemos a llenar los miembros de la variable persona y finalmente los grabamos en la siguiente posición del archivo. *c Cuando salimos del bucle cerramos el archivo (hay que hacerlo siempre) y escribimos un mensaje en pantalla con el número de registros grabados aprovechando el valor de la variable n que usamos como contador. 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 sobre escribirá 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 *c
##c c
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 cuántos 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 último registro como hemos hecho antes y no leer nada más allá para
#$c c
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 sobre escribiendo 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" c x x= cYc!"%""%c&c4$)#c&$%#c x x= xcYc"%&($$2"c!'c!"%""%c&c4$)#c 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 sobre escribí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. x x= cc!'%!# c!Z!&c #%#c 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 sobre escribí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. RProcedimiento ALTA RAñ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
$c c
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 sobre escribirá y se perderá la anterior. Si es una posición vacía más allá del final del archivo, el tamaño crecerá creándose 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... c x x= cc$"#('%!# c W# (&!c#$("$!'c&c #%#c 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
$ c c
PRINT "Nombre.....: "; persona.nombre PRINT "Edad.......:"; persona.edad ELSE PRINT "No se ha encontrado" END IF Lo primero que hacemos es calcular cuántos registros tiene el fichero para ver hasta dónde 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. x x= cc&4$!$"#c&c #%#c 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 sobre escribir 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 sobre escribiendo a la anterior. Vamos a ver cómo sería (Archivos ya abiertos y tipos ya definidos): numregS = (LOF(1) / LEN(persona))
$c c
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 RPara 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 poder 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 sobre escribiendo 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. c x x= /cc !5!# c !c #%#c 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 sobre escribir 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 cómo hacerlo, casi igual al de las modificaciones.
$c c
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 RPara 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 sobre escribimos 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. x x= =cc$!$%!$2"c&c4$)#c&$%#c 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
$c c
WinZip, esto es "comprimir" ficheros y la forma de programar estas operaciones es terriblemente más complicada. ccc -ccc. c c cc cc ccDc ccc . cc c cc. c cJ cc c .cI cc c c -ccc c c c#cc c .cc c J .cccDcc?cc ccc0c?cc -ccc0c cc c cccc -cI cc cc c .cIcc cJ - c #ccc cc ccc c .cc c J .c ccIcccc 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) RSeguimos con el programa... Vamos a ver como funciona esto:
$c c
Lo primero que hacemos, como siempre, es calcular el número de registros que tiene el fichero para saber hasta donde podemos llegar. *c A continuación abrimos un fichero llamado "Agenda.tmp" (La extensión .tmp significa "temporal") y le asignamos el descriptor #2. Este fichero debe ser abierto con la misma estructura que el otro y suponemos que no existe y se crea uno nuevo y vacío al abrirlo. *c Antes de entrar en el bucle inicializamos dos contadores al valor 1, uno nos va a servir para recorrer el fichero viejo y otro para recorrer el nuevo. Empezamos por 1 que es el primer registro. *c Ahora usamos un bucle "Mientras" para recorrer el fichero viejo desde el principio hasta el final. Dentro de este bucle leemos cada registro y si no está completamente vacío lo copiamos al nuevo fichero e incrementamos ambos contadores. Si está vacío no hacemos nada y solo incrementamos el primer contador para poder seguir recorriendo el fichero viejo. *c Para comprobar si el registro está completamente vacío habrá que usar una expresión condicional que puede llegar a ser muy larga porque hay que comprobar cada dato miembro. En nuestro ejemplo esta expresión aparece ocupando dos líneas, pero si la escribes en QBasic tendrá que ser toda seguida en la misma línea, aunque sea muy larga. QBasic soporta líneas de hasta 256 caracteres de larga, si sigue sin caber tendrás que cambiar los nombres de las variables por otros más cortos, aunque en esta ocasión sean menos descriptivos. *c Una vez que salimos del bucle tendremos el fichero "Agenda.dat" tal como estaba y el nuevo "Agenda.tmp" compactado solo con los datos que nos interesan. *c Para proceder al intercambio de los nombres de los ficheros y quedrnos solo con el nuevo podemos seguir estos pasos dc Cerrar los dos ficheros, usando la orden CLOSE sin descriptor. dc Borrar "Agenda.dat" usando la orden KILL, que veremos con detalle más adelante. dc Cambiarle el nombre a "Agenda.tmp" por "Agenda.dat" usando la orden NAME, que también veremos en próximos temas. dc Volver a abrir "Agenda.dat" con el descriptor #1 para seguir trabajando con él normalmente desde el resto del programa. 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. c x x=
$c c
"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
$"c c
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
$#c c
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
$$c c
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. c x x= cc 3c&!c# c !##c&c&!%#c 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
c c
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 cómodo 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. c x x= /cc%!#c"#%($$"#c'!$"!&!#c$"c 4$)#c 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
c c
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 cuáles 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
c c
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 caracteres comodí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$
c c
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.
c | c c| c c
c *c x x< xcc-c % c' cc cIc cc !#$ *c x x< cc-c cc *c x x< cc$cc c c x x< xcc"#%($$2"c % c'!c+#c$"$&!c7c&!&!c &c !#$c Empecemos con un ejemplo "muy malo": CLS Pricipio: PRINT "Esto es el principio" GOTO Final Enmedio: PRINT "Esto es lo de en medio, pero no lo verás" Final: PRINT "Esto es el Final" Que daría como resultado en pantalla: Esto es el principio Esto es el final Ahora veamos como funciona: *c Borra la pantalla. *c Pasa por una etiqueta definida como Principio. *c Escribe "Esto es el principio" *c Busca una etiqueta que se llame "Final" y cuando la encuentra salta y sigue a partir de allí. *c Escribe "Esto es el final". *c Como no hay nada más, el programa termina. En este programa hemos definido tres etiquetas. Para definir etiquetas usamos las mismas normas que para los nombres de variables y al final le colocamos el carácter dos puntos (:). Las etiquetas siempre van al principio de la línea y si usamos etiquetas es
c c
útil encolumnar todas las demás instrucciones más a la derecha, para verlas al vuelo. Podemos definir etiquetas en cualquier módulo de nuestro programa, pero QBasic solo las "encontrará" si están definidas en el módulo principal. Por esto no tiene sentido definir etiquetas dentro de procedimientos y funciones. Las etiquetas no hacen nada, simplemente están ahí. Únicamente nos sirven para poder "Saltar" a ellas usando instrucciones especiales como la famosa GOTO (Go to..., ir a...) que salta incondicional e inmediatamente a la etiqueta que le indiquemos para seguir desde allí con la ejecución del programa. Sencillo ¿No?. En verdad es muy sencillo pero puede dar lugar a toda una variedad de problemas lógicos que pueden ser casi imposibles de detectar, aislar y solucionar. Imagina un programa donde en vez de tres etiquetas haya trescientas y entre ellas haya centenares de instrucciones GOTO que dirigen la ejecución del programa a un sitio lejano de nuestro listado. Una mala planificación del programa seguramente hará que bloques enteros no se lleguen a ejecutar nunca o que se produzcan bucles infinitos que lleguen a bloquear el programa. Por eso a los programas que usan estas instrucciones se les conoce como "Código Espagueti". En los lenguajes de bajo nivel como es el caso de los ensambladores y en lenguajes de guiones muy sencillos como el que se utiliza para construir los ficheros .BAT de MS-DOS se usan intensivamente las etiquetas y las instrucciones de bifurcación o salto incondicional, pero los modernos lenguajes de programación estructurada (QBasic lo es) nos dan la posibilidad de usar estructuras de control iterativas (Bucles PARA, MIENTRAS y REPETIR), así como los procedimientos y las funciones que nos permiten en todo caso saber como funciona nuestro programa para poder solucionar posibles errores. Por esto es muy recomendable 3%!c!c%&!c$#%!c'c(#c&c#%!c %&' 6!c&c !!$2"c$"c"#%($$"#c %c7c#'!# en los lenguajes de alto nivel, salvo en el caso de las instrucciones de manejo de errores que vamos a ver en los siguientes apartados y que son el motivo de esta explicación. c x x< cc"%&($$2"c!c'#c#c Hemos ido viendo a lo largo de los temas anteriores que hay tres tipos de errores: *c Errores de compilación *c Errores de tiempo de ejecución *c Errores lógicos Los primeros suelen ser cosas mal escritas o que no cumplen con las normas de sintaxis del lenguaje de programación. La mayoría de ellos en QBasic serán mostrados conforme vamos escribiendo nuestro código al pasar de línea, o en todo caso al intentar ejecutar el programa. Tendremos que corregirlos modificando lo que este mal para poder iniciar el programa.
c c
Los errores de tiempo de ejecución se producen durante la ejecución del programa y son provocados por intentar hacer algo que no está permitido, como dividir entre cero, o bien porque alguno de los dispositivos del ordenador falle (Memoria agotada, no se encuentra un fichero, la impresora no tiene papel, etc...). Estos errores hacen que el programa se detenga. En QBasic se volverá al editor y se mostrará un recuadro con el mensaje apropiado. En otros lenguajes o en programas ya compilados puede salir desde un simple mensaje hasta una pantalla azul típica de Windows o lo más normal es que el ordenador deje de responder y haya que reiniciarlo. Los errores lógicos se deben a una mala planificación de los algoritmos, lo que da lugar, en el mejor de los casos, a que el programa funcione correctamente pero no haga lo que queremos y no resuelva el problema para el que ha sido diseñado, o lo más normal es que llegue a provocar un error de tiempo de ejecución porque se llene el espacio disponible en memoria o se entre en un bucle infinito. En este tema vamos a ver cómo conseguir que cuando se produzca un error de tiempo de ejecución el programa no quede detenido inmediatamente, sino que se salga de él de una forma un poco más "elegante" o incluso se pueda solucionar el problema y seguir normalmente con la ejecución del programa. Usando las técnicas de programación estructurada que hemos visto en este curso hasta antes del tema de ficheros si hacemos los programas bien NO TIENEN PORQUÉ PRODUCIRSE ERRORES DE TIEMPO DE EJECUCIÓN. Nosotros somos los responsables de evitar que los bucles sean infinitos, de no hacer referencias a índices de arrays que no existen, de que nada se llegue a dividir entre cero, etc... La única fuente potencial de errores son los datos que pedimos al usuario, pero si depuramos los datos de entrada de forma que no se acepten valores no permitidos como vimos en el tema de bucles podemos asegurar en la amplia mayoría de los casos que nuestro programa va a funcionar bien siempre, ya que solo acepta datos válidos. El problema llega cuando empezamos a trabajar con dispositivos externos como son las unidades de disco donde se guardan los ficheros. Por muy bien hecho que esté nuestro programa no podemos evitar que el usuario quiera abrir un fichero que no existe, o de un disquete que todavía no ha introducido en la unidad correspondiente, o que se ha estropeado, que quiera escribir en un CD-ROM, en un disco que no tiene espacio suficiente o que está protegido contra escritura. Sólo en este caso de los ficheros y en otros también especiales como cuando veamos cómo usar las impresoras será necesario y recomendable programar rutinas de manejo de errores de las que vamos a ver en el siguiente apartado. En otros casos no nos merece la pena usar esto ya que las instrucciones GOTO ofrecen una forma especialmente mala de estructurar los programas y podemos despreciar la posibilidad de que se produzca algún error raro como
c c
que se agote la memoria por culpa de otro programa y el nuestro no pueda seguir funcionando. c x x< cc$"%'c&c#c Para activar el control de errores en QBasic usaremos la siguiente instrucción: ON ERROR GOTO linea Dónde linea es el nombre de una etiqueta que se haya definido en el programa principal. Esto quiere decir "Cuando se produzca un error ve inmediatamente a la etiqueta que se llama linea y sigue desde allí". De esta forma conseguimos que el programa no se detenga inmediatamente al producirse un error. Como hemos dicho antes, solo debemos controlar los errores en instrucciones peligrosas como es el caso de las que abren los ficheros (OPEN). Una ver pasadas estas instrucciones debemos desactivar el manejo de errores usando la instrucción: ON ERROR GOTO 0 Lo último de esta instrucción es un cero. A partir de ahora cuando se produzca un error ya no habrá manejo de errores y el programa se detendrá, pero si lo hemos hecho bien no tienen porqué producirse errores. Lo normal es activar el manejo de errores al principio de un procedimiento que, por ejemplo, abra un fichero y desactivarlo al final, antes de salir. De esta forma tenemos claramente delimitado donde usamos el manejo de errores, que estará activo solo mientras se ejecute ese módulo, y no en el resto del programa que no usa instrucciones "conflictivas". Vamos con un ejemplo completo: RPrograma principal CLS INPUT "Nombre del fichero: ", fich$ MuestraTamanno fich$ END manejaerrores: SELECT CASE ERR CASE 53 PRINT "No CASE 68 PRINT "No CASE 71 PRINT "No CASE 76 PRINT "No CASE ELSE PRINT "Se END SELECT
se ha encontrado el archivo" se ha encontrado la unidad de disco" se ha encontrado el disco en la unidad" se ha encontrado el directorio" ha producido el error"; ERR
"c c
END REste procedimiento abre el archivo, muestra el tamaño y cierra REntrada: f$: El nombre del archivo SUB MuestraTamanno (f$) ON ERROR GOTO manejaerrores OPEN f$ FOR INPUT AS #1 PRINT "El fichero ocupa"; LOF(1); "bytes." CLOSE ON ERROR GOTO 0 END SUB Este programa pide al usuario que escriba el nombre de un fichero para abrirlo y mostrar su tamaño usando la función LOF que ya conocemos. Si escribimos un nombre de archivo válido no se activa para nada el manejo de errores y el programa funciona normalmente, nos calcula el tamaño en bytes del archivo y termina al llegar a la instrucción END que hay justo después de la llamada al procedimiento. El resultado podría ser: Nombre del fichero: c:\autoexec.bat El fichero ocupa 219 bytes. Veamos como funciona el programa paso a paso en caso de que el fichero exista y no se produzca el error: *c Entramos al módulo principal. *c Borramos la pantalla con CLS. *c Pedimos al usuario que escriba el nombre de un fichero y lo guardamos en la variable fich$: *c Entramos al procedimiento Muestratamanno y le pasamos la variable fich$ que contiene el nombre que ha escrito el usuario. *c Ya dentro del procedimiento, activamos el control de errores. *c Abrimos el fichero, que existe, para lectura y le asignamos el descriptor #1 *c Calculamos el tamaño del fichero abierto #1 con la función LOF y lo escribimos en pantalla junto con un mensaje. *c Cerramos todos los ficheros abiertos (El único que hay). *c Desactivamos el control de errores y salimos del módulo porque no hay más instrucciones. *c De vuelta al programa principal la siguiente instrucción que encontramos es END que hace que el programa termine, la usamos para que no se ejecuten las siguientes instrucciones que son las del control de errores. Aquí encontramos como novedad la instrucción END que hace que el programa termine de forma normal aunque no hayamos llegado al final del listado. La tendremos que poner siempre en el módulo principal antes de la etiqueta del manejador de errores para evitar que estas instrucciones se ejecuten en caso normal de que no haya un error. En otros casos no es recomendable usar esta instrucción,
#c c
los programas y módulos deben empezar por el principio y terminar por el final del listado. Ahora vamos a ver como funcionaría el programa paso a paso en el caso de que quisiéramos abrir un fichero que no existe y se produjera el error. *c Entramos al módulo principal. *c Borramos la pantalla con CLS. *c Pedimos al usuario que escriba el nombre de un fichero y lo guardamos en la variable fich$: *c Entramos al procedimiento Muestra tamaño y le pasamos la variable fich$ que contiene el nombre que ha escrito el usuario. *c Ya dentro del procedimiento, activamos el control de errores. *c Intentamos abrir el fichero como no existe se produciría un error de tiempo de ejecución que es detectado por el control de errores que continúa inmediatamente con la ejecución del programa a partir de la línea definida como maneja errores en el programa principal. *c Después de esta línea encontramos un SELECT CASE que usa la función ERR. Esta función devuelve el número del error que se ha producido. Cada error tiene un número único que nos sirve para identificarlo y hacer una cosa u otra según lo que haya ocurrido. En este caso lo usaremos para sacar un mensaje adecuado diciendo al usuario lo que ha pasado. *c Después del SELECT CASE, el programa termina, pero lo hace normalmente porque se ha acabado el listado, y no porque se ha producido un error, cosa que hay que evitar a toda costa. Hay que tener claro que al producirse el error el fichero no se ha abierto, y por lo tanto el descriptor #1 no contiene referencia a ningún fichero, por lo que no podemos intentar hacer nada con él, ni siquiera cerrar el fichero. Hay un gran número de códigos de error, puedes ver una lista completa de ellos en la ayuda en pantalla de Qbasic. Nunca es necesario controlarlos todos, por ejemplo si estamos trabajando con ficheros solo pondremos algunos de los relacionados con ficheros. Siempre es conveniente usar algo como CASE ELSE para que quede controlado alguno que se nos haya podido escapar, en este caso bastará con mostrar el número por pantalla para poder identificar el error de alguna forma, pero siempre haciendo que el programa termine bien. Se pueden hacer manejos de errores mucho más elaborados que lleguen a solucionar el problema y el programa pueda seguir funcionando como si nada. Imagina que queremos abrir un fichero .INI dónde está la configuración del programa, y este se ha borrado. En este caso de forma totalmente transparente al usuario podríamos crear uno nuevo vacío o con valores por defecto y dejarlo abierto para que el programa siga trabajando con él normalmente. En este caso usaríamos la instrucción RESUME para continuar con el programa por la línea siguiente a la que produjo el error, una vez que
$c c
ya hayamos abierto el nuevo fichero para escritura, hayamos escrito en él lo que sea, lo hayamos cerrado y lo hayamos vuelto a abrir para lectura, todo esto en el manejador de errores.
c | c cccc c | c Lo último que nos queda por ver en la sección de temas básicos del curso de introducción a la programación es cómo conseguir que nuestro programa sea capaz de iniciar otros programas externos, ya sean ejecutables .EXE u ordenes de MS-DOS internas o externas o bien otros programas de QBasic. Para conseguir lo primero usaremos la instrucción SHELL seguida de la orden de MS-DOS o del programa ejecutable o programa BAT que queremos ejecutar. Vamos con unos cuantos ejemplos: SHELL "dir" Hace que en la pantalla de nuestro programa aparezca el listado del contenido del directorio activo. SHELL "dir c:\basic\juegos" Hace que en la pantalla de nuestro programa aparezca el listado del contenido del directorio c:\basic\juegos. Si este directorio no existe no se producirá ningún error en nuestro programa, simplemente la orden DIR nos sacará un mensaje diciendo que no se ha encontrado el achivo. SHELL "lote.bat" Ejecuta el archivo de proceso por lotes llamado lote.bat. Lo buscará en el directorio activo y si no lo encuentra lo buscará en los directorios que aparecen en la variable de entorno PATH del MS-DOS. Si no lo encuentra sacará el típico mensaje "Comando o nombre de archivo incorrecto", pero en nuestro programa no ocurrirá ningún error. SHELL "winmine" En la mayoría de los casos, iniciará el buscaminas de Windows, un programa ejecutable. Si no lo encuentra pasará lo mismo que en el ejemplo anterior, pero tampoco generará ningún error. SHELL Si no escribimos ningún argumento, se abrirá una instancia de MSDOS dentro de nuestro programa de QBasic. Aquí el usuario podrá ejecutar órdenes como se hace normalmente. Para volver al programa tendrá que escribir EXIT y pulsar Enter. Habrá que advertir esto al usuario antes de entrar para que no se quede "encerrado" si no sabe como salir. También es peligroso ejecutar determinados programas ya que en estos entornos los recursos de memoria son muy limitados. Esta instrucción SHELL nos puede servir para ejecutar ordenes de MS-DOS o pequeños programas que nos pueden hacer falta usar. Si ejecutamos algo de MS-DOS, que no es un sistema multitarea, nuestro programa quedará detenido hasta que este programa termine. En el caso que llamemos a algún programa Windows, como
c c
el caso del buscaminas, nuestro programa lo iniciará y seguirá funcionando. Normalmente no será necesario llamar a programas externos, salvo en situaciones muy específicas, y siempre deberán ser programas sencillos que consuman muy pocos recursos, si llamamos a algún programa muy grande como Windows desde QBasic y fallan los sistemas de seguridad puede que el ordenador se bloquee. También nos puede interesar que nuestro programa termine y pase el control a otro programa de QBasic, por ejemplo en el caso de que sea un menú. Para hacerlo haríamos esto. RUN "Programa.bas" Al ejecutar esta orden nuestro programa termina inmediatamente y QBasic carga e inicia el otro programa Basic. Esta orden equivale a que nosotros detengamos nuestro programa inmediatamente, en el editor abramos el otro programa y lo ejecutemos pulsando F5. Si al usar la orden RUN todavía no habíamos guardado los cambios en el código fuente aparecerá un mensaje del editor pidiéndonos que lo hagamos, esto no es un error, cuando lo hagamos se continuará con el otro programa. Si el programa no existe se producirá un error de tiempo de ejecución. Si el programa no es un programa Basic se producirá un error de compilación al intentar iniciarlo, nuestro programa anterior para entonces ya habrá terminado. Al pasar de un programa a otro se cierran los ficheros que hubiera abiertos y desaparecen todas las variables. En algunos casos, todavía más rebuscados, nos puede interesar seguir trabajando con los ficheros abiertos y con las variables globales que fueron declaradas con la instrucción COMMON. Para conseguir esto en vez de usar la instrucción RUN usaremos la instrucción CHAIN, que trabaja de forma idéntica, excepto en que conserva los ficheros abiertos y reconoce las variables declaradas con COMMON en programas anteriores. Normalmente una buena jerarquía de procedimientos SUB será más recomendable y más estructurada que usar la orden RUN o CHAIN. Estas órdenes solo las deberemos usar en casos muy sencillos como un pequeño menú que nos abra varios programas. La orden CHAIN no la deberemos usar casi nunca y sobre todo no encadenar varios programas ya que perderemos completamente la pista de dónde declaramos las variables y abrimos los archivos, cosa que puede dar lugar a errores muy difíciles de solucionar, como ocurre siempre que no estructuramos bien los programas.
c | c c | c c| c cccc" #c *c *c *c
x xcc(cc cJ-c"V79 x cccc c x cc) c ccc
c c
c x xcc(#c&c'!c4("$2"c"V79c La función INKEY$ nos devuelve el contenido del buffer del teclado, es decir, cual es la última tecla o combinación de teclas que se ha pulsado. Esta función es más primitiva que INPUT, pero no la pudimos ver en el tema de entrada y salida básica porque todavía no sabíamos cómo usar los bucles, algo imprescindible para usar esta función ya que el programa no se detiene como ocurre con la instrucción INPUT. También hay que tener en cuenta que no es algo básico de la teoría de programación, ya que en otros lenguajes puede que funcione de formas muy distintas. Para usar la función lo que haremos es almacenar su valor en una variable, ya que al leer el valor de INKEY$ el carácter que contenga se borra del buffer de teclado y ya no lo veremos más. Vamos con el primer ejemplo: CLS tecla$ = INKEY$ PRINT tecla$ Este ejemplo funciona, aunque al ejecutarlo no lo parezca. Lo que hace es borrar la pantalla, almacenar la última tecla pulsada (durante el tiempo de vida del programa) en la variable tecla$ y a continuación escribirla en pantalla. Lo que pasa es que como el programa no se detiene no nos da tiempo a pulsar ninguna tecla y no aparece nada en pantalla, salvo el conocido rótulo "Presione cualquier tecla y continúe". Para solucionar esto tenemos que recurrir a los bucles y programar lo que se conoce como "mecanismo de espera activa". Allá vamos, el siguiente fragmento de código será de los más usados en juegos y programas interactivos hechos con QBasic: DO tecla$=INKEY$ LOOP WHILE tecla$="" Al hacer esto se entra en un bucle que lee continuamente la entrada de teclado y la almacena en la variable tecla$. No salimos del bucle mientras no pulsemos nada y la variable tecla$ siga estando vacía. Una vez que pulsemos una tecla esta quedará almacenada en la variable para usarla más adenante donde haga falta. Esto ya funciona bien, pero todavía no es infalible. Puede ocurrir que en el buffer del teclado quede alguna pulsación de tecla residual de alguna instrucción de entrada anterior y que al llegar aquí se salte la espera. Para que esto no ocurra tendremos que "limpiar el buffer del teclado" antes de entrar al bucle. Para hacerlo usaremos otro bucle que lee las posibles teclas (y las va borrando) del buffer hasta que se quede vacío. Vamos a ver un ejemplo completo que espera a que pulsemos una tecla y la saca por pantalla. El primer bucle, de tipo WHILE, corresponde a la limpieza del buffer y el segundo, de tipo DO..LOOP, a la espera activa. CLS
c c
WHILE INKEY$<>"" WEND DO tecla$=INKEY$ LOOP WHILE INKEY$="" PRINT tecla$ Como se puede ver, este bucle de limpieza es un bucle WHILE, es decir, un bucle rechazable. Si el buffer ya está vacío ni siquiera entramos, en caso contrario entramos y leemos y borramos la tecla que pudiera tener antes de ir al otro bucle de reconocimiento de teclas. c x cc$"$"%c&c%$'!#c#$!'#c Como hemos dado por supuesto en los ejemplos anteriores las teclas se almacenan en el buffer del teclado usando el código ASCII o la representación del carácter que llevan. Por ejemplo la "a minúscula" se almacena como "a" o su código ASCII que es el 97, la "a mayúscula" lo hace como "A" o 65, el número cinco como "5" o 53, el punto como "." o 46 , el espacio en blanco como " " o 32 y así con todos los caracteres que tenemos en la sección alfanumérica del teclado. Para trabajar con los códigos ASCII usaremos la conocida función CHR$(), de esta forma es lo mismo decir... IF tecla$ = "A" THEN ...que ... IF tecla$ = CHR$(65) THEN De esta forma también podemos identificar otras teclas solo con conocer su código ASCII. Estos son algunos de los más usados: Escape.....: 27 Enter......: 13 Tabulador..: 9 Retroceso..: 8 También es posible identificar las teclas especiales de flechas (Imprescindible para los juegos), las teclas de función y las otras como Inicio, Fin, Insertar, etc. Al pulsar estas teclas se almacenan en el buffer del teclado dos caracteres, siendo el primero el CHR$(0). Para identificar estas teclas no hay más que usar en las comprobaciones CHR$(0)+El carácter que tengan asignado, aquí va una lista de los más usados: Flecha arriba......: Flecha abajo.......: Flecha izquierda...: Flecha derecha.....: F1.................: F2.................: F3.................: F4.................: F5.................:
CHR$(0)+"H" CHR$(0)+"P" CHR$(0)+"K" CHR$(0)+"M" CHR$(0)+";" CHR$(0)+"<" CHR$(0)+"=" CHR$(0)+">" CHR$(0)+"?"
c c
F6.................: F7.................: F8.................: F9.................: F10................: F11................: F12................: Insertar...........: suprimir...........: Inicio.............: Fin................: Retroceder página..: Avanzar página.....:
CHR$(0)+"@" CHR$(0)+"A" CHR$(0)+"B" CHR$(0)+"C" CHR$(0)+"D" CHR$(0)+CHR$(133) CHR$(0)+CHR$(134) CHR$(0)+"R" CHR$(0)+"S" CHR$(0)+"G" CHR$(0)+"O" (Letra o mayúscula) CHR$(0)+"I" (Letra i mayúscula) CHR$(0)+"Q"
Podemos ver una lista completa en la ayuda en pantalla de QBasic en el apartado llamado "Códigos de exploración del teclado". Aquí también aparecen los códigos de teclas combinadas como por ejemplo CONTROL+Z. Vamos a ver un ejemplo final de un programa que reconozca todas las teclas alfanuméricas normales además de Enter, Escape y las flechas que son especiales. En temas siguientes cuando hagamos programas mejores usaremos ampliamente el reconocimiento de teclas. CLS PRINT "Pulsa las teclas que quieras, escape para salir" PRINT DO WHILE INKEY$ <> "": WEND RLimpia buffer de entrada DO tecla$ = INKEY$ LOOP WHILE tecla$ = "" SELECT CASE tecla$ CASE CHR$(0) + "H": PRINT "Has pulsado la flecha arriba" CASE CHR$(0) + "P": PRINT "Has pulsado la flecha abajo" CASE CHR$(0) + "K": PRINT "Has pulsado la flecha izquierda" CASE CHR$(0) + "M": PRINT "Has pulsado la flecha derecha" CASE CHR$(13): PRINT "Has pulsado Enter" CASE CHR$(27): PRINT "Has pulsado Escape, adios" CASE " ": PRINT "Has pulsado la barra de espacio" CASE ELSE: PRINT "Has pulsado la tecla " + tecla$ END SELECT LOOP UNTIL tecla$ = CHR$(27) Observa que este programa no reconoce las teclas de función, entre otras, para hacerlo no hay más que añadir las instrucciones CASE con los códigos correspondientes.
c c
c x cc)!$c!(#!#c"c'#c !!#c Una forma muy sencilla de detener la ejecución de un programa, por ejemplo para que el usuario pueda leer algo es usar la instrucción SLEEP. Al usar esta instrucción el programa se detiene hasta que el usuario pulse una tecla. Si añadimos un número a continuación, por ejemplo: SLEEP 10 Ocurrirá que el programa se detendrá por un máximo de 10 segundos, continuando pasado este tiempo automáticamente aunque el usuario no pulse ninguna tecla. Esta instrucción funcionará en la mayoría de los casos, pero puede ocurrir que alguna vez no llegue a detener el programa porque hay datos residuales en el buffer de entrada, en estos casos podremos usar lo que hemos visto en el apartado anterior: Limpieza del buffer+Espera activa, es decir esto: WHILE INKEY$<>"":WEND DO:LOOP WHILE INKEY$="" Esta estructura la podemos ampliar para hacer una pregunta al usuario y dejarle responder Sí o No pulsando S o N, por ejemplo. WHILE INKEY$<>"":WEND DO tecla$=INKEY$ LOOP UNTIL tecla$="S" OR tecla$="N" Aquí entramos en el bucle de espera activa y no salimos hasta que el usuario pulse la S o la N, y después más adelante en el programa viendo el valor de tecla$ ya decidiríamos entre hacer algo o no. Pero todavía hay un problema, este ejemplo tal como está sólo reconoce la S y la N mayúsculas, por lo que si el usuario tiene el "Bloqueo de mayúsculas" desactivado puede que no se le ocurra como seguir. Para arreglar el problema usaremos la función UCASE$ que convierte todas las letras (Menos la Ñ y las vocales acentuadas) a mayúsculas. Vamos con el ejemplo completo: CLS PRINT "Quieres hacer no se qué? S/N: " WHILE INKEY$<>"":WEND DO tecla$=UCASE$(INKEY$) LOOP UNTIL tecla$="S" OR tecla$="N" IF tecla$="S" THEN PRINT "Has dicho que sí" ELSE PRINT "Has dicho que no" END IF Ampliando más este ejemplo podríamos construir un menú "cutre". Si dejamos al usuario la posibilidad de pulsar más teclas, normalmente números, y después los comprobamos con un SELECT CASE
c c
podríamos decidir entre hacer varias cosas. Es una estructura similar a la usada en el ejemplo de reconocer teclas del apartado anterior.
| c cc$ c | cc| c c| c *c *c
xcc"?c c4-c"& cc$cc
xccW#c!'!%# c4("$2"c"&c Cuando diseñamos un programa informático lo que intentamos es resolver un problema obteniendo un resultado lo más exacto y fiable posible, usando la gran precisión de los cálculos que hacen los ordenadores. De hecho, una de las normas básicas para asegurar que un algoritmo funciona correctamente es poder asegurar y demostrar que para unos mismos datos de entrada los resultados o datos de salida van a ser siempre los mismos. En algunos casos como son los videojuegos nos puede interesar que suceda justamente lo contrario de lo que se ha dicho en el párrafo anterior, es decir, que cada vez que ejecutemos el programa sucedan cosas distintas y aparentemente impredecibles. Otras aplicaciones donde tendría que suceder esto serían programas simuladores de cálculos y fenómenos científicos y algunas aplicaciones matemáticas o estadísticas especiales. Para poder llegar a estas situaciones nos encontramos con el terrible problema de conseguir que el ordenador (tan perfecto, exacto, preciso, etc...) sea capaz de generar números de cualquier manera sin responder a ningún orden aparente. Esto técnicamente es imposible y por lo tanto en vez de hablar de números aleatorios sería más correcto llamarlos números pseudo-aleatorios porque no son completamente impredecibles, pero lo son casi, lo suficiente como para resolver nuestras necesidades. Para generar estos números (pseudo-) aleatorios el ordenador puede usar mecanismos tales como medir el tiempo entre dos pulsaciones de teclas, o el necesario para enviar o recibir un dato a través de una red. Como esto puede ser difícil de conseguir en algunos casos, es mucho más cómodo usar lo que se conoce como "generador de números aleatorios" que es una formula matemática que al aplicarla repetidas veces pasándole los valores anteriores nos va devolviendo una serie de números sin orden aparente. Una de estas fórmulas se llama "Generador de Marsaglia" y viene a ser algo así como: zN = (2111111111 * zN-4 + 1492 * zn-3 + 1776 * zn-2 + 5115 * zn-1 + C) MOD 232 C = FLOOR((2111111111 * zN-4 + 1492 * zn-3 + 1776 * zn-2 + 5115 * zn-1 + C) / 232) Esta fórmula nos devolvería una serie de números que no empezaría a repetirse hasta que lo hayamos calculado 3 * 10 elevado a 47 veces, un 3 con 47 ceros detrás, más que suficiente. Pero que nadie se asuste, de hecho no se ni si están bien copiados los números. En
c c
QBasic (y en otros lenguajes de programación) no nos hace falta programar tantos relíos, basta con usar el valor que nos devuelva la función RND cada vez que queramos obtener un número aleatorio. Vamos con un ejemplo de los más sencillo del curso de programación: CLS PRINT RND Al ejecutarlo se escribiría en pantalla el número aleatorio que devuelva la función RND, un valor decimal (casi) impredecible, por ejemplo: .7055475 Ya lo tenemos ahí, un número generado por el ordenador sin responder a ningún criterio aparente (Lleva un cero delante, pero QBasic se lo quita al escribirlo en la pantalla y solo se ve el punto decimal), pero todavía hay un problema, ejecuta el programa varias veces y mira el resultado. Puedes comprobar que siempre sale el mismo número, ya no es tan impredecible. Esto se debe a que no hemos inicializado el generador de números aleatorios de QBasic. Si tratas de descifrar algo de la fórmula de Marsaglia que hay más arriba observarás que para calcular un número pseudo-aleatorio se necesitan algunos de los calculados anteriormente. Esto va muy bien si ya los hemos calculado, pero si es la primera vez no los tenemos. En este caso tendremos que usar lo que se conoce como "Semilla", que es un número arbitrario usado para que empiece a funcionar el generador. Si este número es el mismo siempre los números aleatorios devueltos serán siempre una misma serie, por lo tanto hay que usar como semilla un número lo más "aleatorio" posible como por ejemplo el número de segundos que han pasado a partir de las doce de la noche, cosa que nos devuelve la función TIMER. Para inicializar el generador de números aleatorios usaremos la instrucción RANDOMIZE seguida de la semilla, normalmente TIMER. Vamos con el ejemplo completo: RANDOMIZE TIMER CLS PRINT RND Ahora sí que cada vez que ejecutemos el programa tendremos un número distinto. El valor solo se repetiría si ejecutamos el programa otro día a la misma hora, mismo minuto, mismo segundo. Algo difícil de conseguir y un riesgo aceptable en programas que no necesiten elevadas medidas de seguridad como los nuestros, pero no en otras aplicaciones como pudieran ser máquinas recreativas. De hecho, en sistemas más serios como Lunux se puede observar como al apagar el ordenador se guarda en algún sitio la semilla aleatoria para poderla seguir usando la próxima vez. La instrucción RANDOMIZE TIMER es algo así como remover las bolas en un sorteo de lotería, debemos usarla una vez al principio de los programas que usen la función RND, pero no es necesario usarla
"c c
más, de hecho repetirla en sitios como dentro de un bucle podría resultar contraproducente. El valor devuelto por RND va a ser un número decimal de precisión sencilla mayor o igual que cero y menor que uno, es decir, alguna vez podría salir el cero, pero nunca saldrá el uno. Este valor lo podemos multiplicar o redondear para adaptarlo a nuestras necesidades. Vamos con un programa que contiene dos ejemplos: CLS RANDOMIZE TIMER valorDado = INT(RND*6)+1 PRINT "Hemos lanzado un dado y ha salido"; valorDado IF RND > 0.5 THEN PRINT "Hemos lanzado una moneda y ha salido RcaraR" ELSE PRINT "Hemos lanzado una moneda y ha salido RcruzR" END IF Que daría este resultado (Con estos u otros valores): Hemos lanzado un dado y ha salido 4 Hemos lanzado una moneda y ha salido RcruzR En el primer caso multiplicamos el valor de RND por seis, ya tenemos un número decimal que puede ir desde 0 a 5.999 (Nunca 6). A continuación lo redondeamos hacia abajo usando la función INT, ya tenemos un entero entre 0 y 5. Como queremos un valor posible entre 1 y 6, que son las caras que tienen los dados, no hay más que sumarle 1. En el segundo caso lo que hacemos es comprobar el valor de RND para hacer una cosa u otra. Como en este caso queremos que las dos partes del IF tengan las mismas posibilidades ponemos el punto de comprobación en la mitad, en 0.5. Si quisiéramos variar las posibilidades no tendríamos más que cambiar ese número. Si queremos más de dos posibilidades podemos usar un SELECT CASE con varios intervalos. Por último vamos a ver como conseguir que RND nos repita el último número que generó sin tener que grabarlo en ninguna variable. Para conseguirlo bastaría con escribir RND(0) en vez de RND. Vamos con un ejemplo: CLS RANDOMIZE TIMER PRINT "Un número aleatorio.............:"; RND PRINT "Repitamos el mismo número.......:"; RND(0) PRINT "Otra vez más....................:"; RND(0) PRINT "Ahora un número aleatorio nuevo.:"; RND PRINT "Vamos a sacarlo otra vez........:"; RND(0) El resultado podría ser (con otros números): Un número aleatorio.............: .2051972 Repitamos el mismo número.......: .2051972 Otra vez más....................: .2051972 Ahora un número aleatorio nuevo.: .1969156 Vamos a sacarlo otra vez........: .1969156
#c c
Como se puede ver, si llamamos a RND sin argumentos, como hemos hecho hasta ahora, nos da un número aleatorio, pero si lo hacemos pasándole como argumento el cero entre paréntesis (Tiene que ser obligatoriamente el cero) lo que haces es repetir el mismo número en vez de calcular uno nuevo, que sería el siguiente de la serie. Si usamos RND(0) por primera vez en el programa, sin haber usado antes RND, no pasa nada, tendremos un número aleatorio. Esto no se utiliza demasiado, pero en algún caso podemos ahorrarnos una variable y una instrucción de asignación si queremos usar el mismo número en varios sitios. cc$"%'c&c%c El ordenador lleva instalado un reloj digital que usa para muchas cosas, como por ejemplo para almacenar en los directorios la fecha y hora en que se guardó cada archivo. Por supuesto nosotros también podemos utilizar este valor en nuestros programas. La forma más sencilla de obtener la hora ya la hemos visto en el apartado anterior. Es usar el valor devuelto por la función TIMER, un entero largo con los segundos que han pasado desde las doce de la noche. Con esta función ya podemos hacer un programa que nos salude de distinta forma dependiendo del momento del día que sea. Allá vamos... CLS SELECT CASE TIMER CASE IS < 28800: PRINT "Buenas madrugadas" CASE 28801 TO 43200: PRINT "Buenos días" CASE 43201 TO 72000: PRINT "Buenas Tardes" CASE ELSE: PRINT "Buenas noches" END SELECT El resultado, si ejecutamos el programa a las diez de la mañana sería: Buenos días Es una forma de que nuestro programa sea algo "inteligente", aunque todavía se podría mejorar (mucho). Lo que hemos hecho es ver cuantos segundos han pasado a las ocho de la mañana, a mediodía y a las ocho de la tarde para definir los intervalos, nada más. También podemos usar la función TIMER para cronometrar el tiempo que tarda en pasar algo. Bastaría con almacenar la hora en una variable antes de empezar, hacer lo que sea, y al terminar almacenar la hora en otra variable y restarlas. Obtendríamos el número de segundos. Vamos a cronometrar lo que tarda el ordenador en escribir en pantalla los números del 1 al 10.000. Allá vamos... CLS inicio=TIMER FOR n = 1 TO 10000 PRINT n NEXT final = TIMER tiempo = final - inicio PRINT "Ha tardado"; tiempo; "segundos"
$c c
Este programa, al final de la tira de 10.000 números nos daría un mensaje con el número de segundos que ha tardado. Todo funcionaría bien excepto si durante la ejecución del programa han dado las doce de la noche, obteniéndose en este caso un número negativo. Para solucionar el problema habría que colocar la siguiente línea justo antes de sacar el mensaje. Lo que hacemos es sumarle todos los segundos de un día si ha dado negativo. IF tiempo < 0 THEN tiempo = tiempo + 86400 Ahora vamos a recordar dos funciones que nos devuelven la fecha y la hora en modo texto, son DATE$ y TIME$. CLS PRINT "Hoy es "; DATE$ PRINT "Son las "; TIME$ Esta daría como resultado algo como: Hoy es 01-20-2011 Son las 10:42:35 Con la hora no hay nada que aclarar, pero la fecha está en formato americano, primero el mes, después el día y al final el año. El mes y el día siempre con dos cifras y el año con cuatro. Estas funciones pueden ser más intuitivas pero para manejar las cifras las tenemos que separar usando la función MID$. Vamos con el ejemplo: CLS PRINT "El día es "; MID$(DATE$, 4, 2) PRINT "El mes es "; MID$(DATE$, 1, 2) PRINT "El año es "; MID$(DATE$, 7, 4) PRINT PRINT "La hora es "; MID$(TIME$, 1, 2) PRINT "Los minutos son "; MID$(TIME$, 4, 2) PRINT "Los minutos son "; MID$(TIME$, 7, 2) Y el resultado podría ser: El día es 20 El mes es 01 El año es 2011 La hora es 10 Los minutos son 42 Los minutos son 35 Muy sencillo. Lo único que hemos hecho es "recortar" con la función MID$ el trozo de cadena donde está cada cosa, que siempre va a estar en el mismo sitio, y separarlo para hacer con él lo que queramos. Y si nos hiciera falta sumarlos o compararlos podríamos convertirlos a enteros usando la función VAL. Aplicando esto mismo, vamos con un ejemplo de una función a la que vamos a llamar FECHA$ y nos va a devolver la fecha actual como la usamos normalmente en castellano: Día, mes y año separados por barras. FUNCTION FECHA$ aux$ = MID$(DATE$, 4, 2) aux$ = aux$ + "/" + MID$(DATE$, 1, 2) aux$ = aux$ + "/" +MID$(DATE$, 7, 4)
c c
FECHA$ = aux$ END FUNCTION Al llamarla desde cualquier parte de nuestro programa nos devolvería, en formato cadena algo como 20/01/ 2011, que queda más bonito que 01-20- 2011. Para terminar vamos a ver como cambiar la fecha y hora del sistema desde nuestro programa. Algo que no es habitual ni recomendable, pero que en algún caso nos puede hacer falta. Sería usando las instrucciones, no funciones sino instrucciones, DATE$ y TIME$ y asignándoles cadenas que contengan la fecha o la hora en el formato adecuado. Vamos con unos ejemplos: DATE$ = "04-20-2011" DATE$ = "04-20-92" DATE$ = "04-06-1994" TIME$ = "10:24:35" TIME$ = "10:24" TIME$ = "10" La fecha podemos establecerla poniendo siempre el mes primero y después el día y el año, este último dato puede tener dos o cuatro cifras. En el último ejemplo podríamos dudar entre si la fecha es el 4 de junio o el 6 de abril, se almacenaría este último, 6 de abril. Cuidado con esto, siempre mes, día, año. En el caso de la hora si no ponemos los minutos o los segundos se entenderán que son 00. Si intentamos asignar una fecha o una hora no válida se producirá un error de tiempo de ejecución. Normalmente no es recomendable modificar estas cosas desde los programas, si lo hacemos mal y no nos damos cuenta estaremos un tiempo usando el ordenador con el reloj mal, provocando problemas en los directorios, los antivirus, el planificador de tareas programadas, etc.
| c cc cc c c | c *c *c *c
xcc! cc c cc cc' cJ-cc-c&9 cc cJcc Dcc
c xcc!$$!c&c'!#c$!&"!#c&c$!!$%#c En QBasic el manejo de cadenas de caracteres es extremadamente sencillo. En otros lenguajes de programación como es el caso de C las cadenas se tratan como un array de datos de tipo carácter (un tipo que no tenemos en QBasic) de longitud aproximadamente igual al número de caracteres de la cadena, y por lo tanto su longitud máxima está siempre limitada. También esta forma de trabajar implica que para hacer algo tan simple como una asignación de cadenas en esos lenguajes haya que recurrir a funciones especiales del lenguaje y no podamos usar directamente el operador de asignación, el signo igual, como si se tratara de números.
c c
En este tema vamos a ver con detalle algunas funciones de QBasic relacionadas con cadenas que nos van a servir para manejar los datos de este tipo que nos hagan falta en nuestro programa. Vamos a recordar un poco lo que ya sabemos de las cadenas: Al igual que ocurre con las otras variables, no es necesario declararlas. Basta con usar un nombre de variable terminado por el carácter dólar ($). En este caso tendremos una cadena de texto de longitud variable. En algunos casos nos puede interesar que las cadenas sean de longitud fija para ahorrar memoria (Unos 10 bytes por cadena). En este caso bastaría con declararlas usando: DIM nombreVariable AS STRING * 25 Dónde el número que va a continuación del asterisco es la longitud máxima de la cadena. Es especialmente recomendable declarar las cadenas como de longitud fija en el caso de los arrays. Por ejemplo si hacemos: DIM tabla (1 TO 40, 1 TO 3) AS STRING * 5 Nos estamos ahorrando unos 120 bytes en todo el array, además de que el manejo de cadenas de tamaño fijo por parte del ordenador es algo más rápido por tratarse de estructuras estáticas. En el caso de las estructuras de datos definidas por el usuario (tipos registro) que incluyan cadenas, estas siempre tendrán que declararse con una longitud determinada. c cc'!c4("$2"cc"#%($$2"c&9c Una de las funciones más útiles que incluye QBasic para el manejo de cadenas de caracteres es MID$. Ya la hemos visto anteriormente en varios ejemplos, pero vamos a hacerlo ahora con más detalle. Esta es su sintaxis. MID$("cadena", inicio, longitud) Esta función lo que hace es extraer una porción de una cadena. Le debemos pasar tres argumentos: *c "cadena" es una cadena de caracteres entre comillas o bien el nombre de una variable de cadena cuyo valor va a ser utilizado por la función, o una expresión que devuelva como resultado una cadena. *c inicio es la posición del primer carácter que queremos sacar de la cadena, el carácter de más a la izquierda es el 1. Si este valor es superior al tamaño de la cadena, la función MID$ devolverá como resultado una cadena vacía. *c longitud es el tamaño del trozo de cadena que queremos extraer. Si se sobrepasa el final de la cadena no ocurre nada, sólo se devolverá lo que se pueda de antes del final. Vamos con unos ejemplos: CLS miCadena = "Hecho en Ronda, Ciudad Soñada" PRINT MID$(miCadena, 1, 5) PRINT MID$(miCadena, 5, 3) PRINT MID$(miCadena, 15, 1)
c c
PRINT MID$(miCadena, 26, 2) PRINT MID$(miCadena, 300, 1) PRINT MID$(miCadena, 10, 3000) El resultado sería: Hecho o e , ña Ronda, Ciudad Soñada Esta función nos puede servir para extraer un determinado carácter de una cadena. Algo que puede parecer trivial, pero que nos va a simplificar mucho determinados problemas. Vamos con un ejemplo tonto: CLS miCadena = "LMXJVSD" INPUT "Escribe un número del 1 al 7: ", num PRINT "El"; num; "º carácter de "; miCadena; " es "; MID$(miCadena, num, 1) Un resultado posible sería: Escribe un número del 1 al 7: 4 El 4 º carácter de LMXJVSD es J Muy sencillo. Sólo decir que no hemos depurado el dato de entrada y que si escribimos un cero o negativo habrá un error. Vamos con otro ejemplo más útil que utiliza esta misma técnica para determinar la letra del DNI. Para hacer esto lo que hay que hacer es comparar el resto del número dividido entre 23 con una serie de letras ordenadas de una forma característica y dar la que corresponda. CLS INPUT "Escribe el número del DNI: ", dni& PRINT "La letra es: ": MID$("TRWAGMYFPDXBNJZSQVHLCKE", (dni& MOD 23) + 1, 1) El resultado podría ser: Escribe el número del DNI: 74926208 La letra es: M Podemos ver que el cálculo se hace en una sola linea de código usando la instrucción MID$. *c El primer argumento que le pasamos a MID$, la cadena de caracteres, es la tira de letras ordenadas de forma característica. *c La posición de inicio es el cálculo propiamente dicho. Le sumamos uno porque la primera letra es la 1 y no la 0. *c El tamaño es 1 porque siempre queremos una y sólo una letra. Es importante ver que la variable dónde guardaremos el número debe ser de tipo entero largo. Si la usamos como de tipo real (sin poner el &) se redondeará y no saldrá bien el cálculo. Con esta técnica el algoritmo ha resultado sumamente corto. A lo mejor sería más sencillo o intuitivo haberlo resuelto usando un vector de caracteres para poder acceder a sus posiciones individuales, o bien
c c
un SELECT CASE. Con cualquiera de estas soluciones el listado del programa hubiera sido mucho más largo. Tendríamos 23 posibilidades en el SELECT CASE o bien 23 asignaciones al vector. Este problema del DNI es muy frecuente. Le falta depurar los datos de entrada y convertirlo en forma de función, para que sea mucho más portable. Para terminar con MID$ hay que decir que además de ser una función, se puede usar también como instrucción para modificar la propia cadena. La sintaxis sería MID$( VariableCadena$, inicio, longitud) = Cadena$ El funcionamiento es parecido, salvo unas cuantas diferencias: *c El primer argumento tiene que ser una variable de cadena y no un literal entre comillas ni una expresión. *c El valor de inicio no puede ser mayor que la longitud de la cadena, en este caso se produciría un error de tiempo de ejecución. Vamos con un ejemplo: miCadena$ = "Hecho en Ronda" PRINT miCadena$ MID$(miCadena$, 10, 5) = "Soria" PRINT miCadena$ MID$(miCadena$, 10, 5) = "Sevilla" PRINT miCadena$ MID$(miCadena$, 7, 8) = "aquí" PRINT miCadena$ El resultado sería... Hecho en Ronda Hecho en Soria Hecho en Sevil Hecho aquíevil Hay que tener en cuenta que la longitud del tramo de cadena a reemplazar queda definido por el valor del tercer parámetro, y no por el tamaño de la expresión de cadena que asignamos. Si este es mayor se cortará, y si no llega sólo se usará lo que haya, quedando lo demás como estaba. De esta misma forma la cadena nunca aumentará o disminuirá su longitud total usando esta función. Como mucho podremos disminuir su longitud aparente usando espacios en blanco, pero realmente seguirán estando ahí formando parte de la cadena. MID$ se usa mucho más como función que como instrucción. Hay que tener claro para que sirve cada cosa y cuando se está usando una u otra. Como función va siempre a la derecha del operador de asignación o dentro de una expresión, y como instrucción va siempre al principio de la línea de código.
c c
c cc%!#c4("$"#c&c!"5c&c$!&"!#c QBasic nos ofrece varias funciones útiles para hacer operaciones con cadenas de caracteres. Una de las más útiles es MID$ a la que hemos dedicado el apartado anterior completo. Aquí van otras: Empecemos con dos funciones algo parecidas a MID$, pero menos avanzadas. LEFT$("cadena", numCaracteres) RIGTH$("cadena", numCaracteres) La primera de ellas devuelve un determinado número de caracteres del principio de la cadena, de la izquierda, y la otra los devuelve del final, de la derecha. Siempre en el mismo orden en que están en la cadena. Se puede hacer referencia tanto a una cadena literal entre comillas o a una variable o expresión de tipo cadena. Si el número de caracteres especificado es mayor que la longitud total de la cadena se devolverá la cadena entera. Si es cero se devolverá una cadena vacía, y si es negativo habrá un error. cadena$ = "Hecho en Ronda" PRINT LEFT$(cadena$, 5) PRINT RIGHT$(cadena$, 5) Daría como resultado: Hecho Ronda Ahora vamos con otras que convierten a mayúsculas y minúsculas... UCASE$("Cadena") LCASE$("Cadena") UCASE$ convierte todas las letras que haya en la cadena, variable de cadena o expresión, a mayúsculas (Upper Case), y LCASE$ a minúsculas (Lower Case). Es importante tener en cuenta que no se reconocen como letras ni los acentos ni la eñe ni la u con diéresis (ü) y por lo tanto no se convierten correctamente. Vamos con un ejemplo. cadena$ = "Una cigüeña en un balcón de África" PRINT UCASE$(cadena$) PRINT LCASE$(cadena$) Daría: UNA CIGüEñA EN UN BALCóN DE ÁFRICA una cigüeña en un balcón de África Ahora vamos con otras dos funciones que eliminarán los espacios en blanco que pueda haber en los extremos de una cadena. LTRIM$("Cadena") RTRIM$("Cadena") LTRIM$ elimina los espacios que puede haber delante (A la izquierda, left) y RTRIM$ los que pueda haber por el final (A la derecha, right). Esta última función es muy útil para trabajar con datos almacenados en cadenas de longitud fija (Como las que se usan en los tipos de datos definidos por el usuario para los registros). Estas cadenas siempre van rellenas con espacios hasta ocupar su longitud total, y si las manejamos con todos estos espacios pueden pasar cosas como
c c
que fallen las comparaciones o que pasen cosas imprevistas en los diseños de pantallas. Ambas funciones pueden ser útiles para depurar datos introducidos por teclado en los que el usuario haya podido escribir espacios inútiles antes o después. Vamos con un ejemplo: cadena$ = " Hola! " PRINT "*"; LTRIM$(cadena$); "*" PRINT "*"; RTRIM$(cadena$); "*" Y el resultado: *Hola! * * Hola!* En Visual Basic se dispone de una función TRIM$ que elimina los espacios tanto delante como detrás de la cadena. Aquí no la tenemos, pero su programación sería muy sencilla construyendo una nueva función a partir de estas dos. Ahora vamos con una función que nos devuelve una cadena llena con el número de espacios que le digamos SPACE$(num) Puede parecer inútil, pero nos ayudará bastante en el diseño de pantallas. Por ejemplo: PRINT "Hola"; SPACE$(40); "Que hay" Daría: Hola Que hay O el siguiente ejemplo más elaborado: CLS FOR n = 0 TO 5 PRINT SPACE$(5 - n); "/"; SPACE$(n + n); "\" NEXT Dibujaría esta figura: /\ / \ / \ / \ / \ / \ Vamos con otra función parecida, pero que en vez de devolver una cadena de espacios la devuelve del carácter que nosotros le digamos: STRING(Longitud, "carácter") o bien STRING(Longitud, Código-ASCII) Como se puede ver podemos especificar el carácter que queremos usando una cadena o bien usando el número de su código ASCII. Esto es especialmente útil cuando queremos dibujar un carácter que no aparece en el teclado. Vamos con unos ejemplos: CLS PRINT STRING$(10, "*") PRINT STRING$(5, 60) PRINT STRING$(15, "RONDA")
c c
PRINT STRING$(8, 126) Que daría ********** <<<<< RRRRRRRRRRRRRRR ~~~~~~~~ Al igual que la anterior, esta función nos será muy útil para construir diseños de pantallas, pero hay que tener cuidado de no confundir su nombre STRING$ con el de tipo de datos cadena que es STRING sin el dólar detrás. Vamos ahora con otra función que en vez de devolvernos la cadena con cierta modificación nos va a devolver un número que corresponde a la longitud (Número de caracteres) de la cadena. LEN("cadena") Vamos con un ejemplo que en combinación con la función STRING$ que acabamos de ver nos subraye una palabra usando guiones. CLS INPUT "Escribe algo: "; cad$ PRINT " "; STRING$(LEN(cad$), "-") Daría algo como: Escribe algo: YA ESCRIBO ALGO --------------Esta función LEN también nos devuelve el espacio en bytes que ocupa en memoria cualquier variable. Basta con pasarle como parámetro el nombre de una variable que no sea de cadenas. A esta categoría de funciones de manejo de cadenas habría que añadir otras que ya hemos visto como son DATE$ y TIME$, así como algunas menos utilizadas como HEX$ y OCT$ que convierten un número a sistemas de numeración en base 16 y en base 8 respectivamente, pero ya que el resultado es en forma de cadena no nos servirá para hacer ningún cálculo. Combinando estas funciones podemos construir otras más potentes que nos hagan cosas como por ejemplo dibujar recuadros o centrar textos. Esto es lo que se verá en el tema siguiente de manejo de pantalla del texto.
| c c c cc |c cc| |c *c *c *c *c *c *c *c *c
xcc-c ccJ cc: cc cccc c
cc%:cc ccJc /cc$ cc J =cc$ c cc
"c c
xcc"%&($$2"c!c'#c"%4!$#c&c%8%c A estas alturas del siglo XXI casi todos los ordenadores personales utilizan modernos entornos gráficos como son Windows o los también conocidos KDE y GNOME en Linux. Estos sistemas pueden representar cualquier imagen a partir de pequeños puntos que pueden ser de uno de los millones de colores soportados. Para aplicaciones más sencillas, como es el caso del entorno de QBasic, se utiliza lo que se denomina Interfaz de texto, que es mucho más fácil de controlar por el ordenador y más que suficiente para determinadas aplicaciones. En todos los ejemplos que hemos hecho hasta ahora hemos podido comprobar como los resultados se iban presentando en la pantalla de forma secuencial, es decir, unos debajo de otro conforme se iban generando. Cuando se alcanzaba la parte baja de la pantalla todo el contenido subía automáticamente para dejar una nueva línea vacía abajo. Algunas operaciones más "sofisticadas" que hemos llegado a hacer han sido borrar todo usando la instrucción CLS o encolumnar la presentación de algunos listados ajustando los argumentos que le pasamos a la instrucción PRINT. De todas formas los resultados siempre se han visto de color gris sobre fondo negro. Muy triste. En este tema vamos a ver cómo se puede conseguir escribir exactamente en la posición de la pantalla que queramos, así como en distintos colores de letra y de fondo. También usaremos unos caracteres especiales conocidos como "Semigráficos" para dibujar líneas y recuadros que den más consistencia a nuestras presentaciones. cc#$"!c'c$(#c"c'!c!"%!''!c El cursor es un objeto que se puede mover por la pantalla y su posición indica el sitio exacto dónde se escribirá lo siguiente. En los procesadores de textos o en el propio editor de QBasic, es la "barrita" intermitente por donde van apareciendo las letras que vamos escribiendo en el teclado. En cualquier programa en modo texto el cursor siempre va a existir y va a indicar el lugar dónde se va a escribir lo siguiente que haya que escribir, ya sea porque lo haga el usuario con el teclado o bien el propio programa atendiendo a sus instrucciones de salida. En los programas de QBasic sólo es visible cuando se pide al usuario que escriba algo usando la instrucción INPUT. El resto del tiempo está ahí en su sitio, pero no lo vemos. Cuando usamos cualquier instrucción PRINT sin punto y coma al final se escribe lo que sea y el cursor invisible pasa al principio de la siguiente línea a la espera de la siguiente instrucción de escritura. De esta forma todos los resultados se van mostrando uno debajo de otro línea por línea. La pantalla normal en modo texto que usamos aquí está compuesta por 25 líneas de 80 caracteres de ancho.
#c c
La primera línea, la de más arriba es la número 1 y la de abajo es la 25. La columna de más a la izquierda es la 1 y la de más a la derecha, la última, es la 80. Para llevar el cursor a cualquiera de estas posiciones y escribir en la parte de la pantalla que queramos no hay más que usar la siguiente instrucción: LOCATE linea, columna De esta forma tan sencilla, usando una instrucción LOCATE delante de cada PRINT o INPUT ya podemos controlar exactamente donde va a salir cada cosa. Vamos con un ejemplo: CLS LOCATE 22, 30 PRINT "Esto va a salir abajo" LOCATE 3, 35 PRINT "Y esto arriba" LOCATE 12, 1 PRINT "A la izquierda" LOCATE 14, 67 PRINT "A la derecha" LOCATE 13, 37 PRINT "Centro" SLEEP Y este sería el resultado. El recuadro representa al borde de la pantalla.
$c c
Cómo se puede ver, las frases ya no aparecen escritas de arriba a abajo en el mismo orden en que están escritas en el listado del programa. Cada una aparece en el sitio donde la anterior instrucción LOCATE ha llevado el cursor de texto. Para ajustar estas posiciones no hay más que variar los números de fila y columna de las instrucciones LOCATE. La instrucción SLEEP que aparece al final es para que el rótulo "Presione cualquier tecla y continúe." no aparezca hasta que no pulsemos una tecla. Esto será muy común para que no se nos estropeen los diseños de pantalla. Otra cosa que nos va a estropear los diseños de pantalla va a ser el desplazamiento vertical que hace automáticamente el interfaz en modo texto para que conforme vamos escribiendo lo anterior se desplaza hacia arriba, como ha venido pasando en los programas que hemos hecho hasta ahora. Esto hace que si escribimos algo en la línea 24, las líneas 1 a 24 suban una posición, desapareciendo lo que haya en la primera línea. Y si escribimos algo en la línea 25, las líneas 1 a 24 subirán 2 posiciones, desapareciendo lo que hubiera escrito en las dos primeras líneas. La solución más cutre para salvar este problema sería no escribir nunca en las líneas 24 y 25, pero hay otra mejor. Poner un punto y coma al final de todas las instrucciones PRINT o a continuación de la palabra INPUT en las instrucciones de entrada, siempre que estén situadas en una de estas dos últimas líneas. De esta forma evitaremos que al terminar la instrucción el cursor baje y se produzca el desplazamiento vertical automático. Ahora ya podemos realmente escribir en cualquier parte de la pantalla usando la instrucción LOCATE. Vamos con un ejemplo que aclare esto del desplazamiento automático. CLS LOCATE 1, 20: PRINT "Principio" LOCATE 25, 20: PRINT "Final" SLEEP Si ejecutas este fragmento de código observarás que la palabra PRINCIPIO no aparece en la pantalla, ya que tras escribir en la línea 25 se produce este desplazamiento automático. Para solucionar el problema añadimos un punto y coma al final de la instrucción PRINT conflictiva: CLS LOCATE 1, 20: PRINT "Principio" LOCATE 25, 20: PRINT "Final"; SLEEP Y podrás observar como aparecen ambas palabras escritas en la pantalla, una arriba y otra abajo, justamente en las posiciones que hemos especificado. Problema solucionado. Un último comentario. En los dos fragmentos anteriores has podido observar que las instrucciones LOCATE y PRINT van en la misma línea
c c
separadas por el carácter "Dos puntos" (:). Esto es común hacerlo para acortar el listado, ya que estas dos instrucciones casi siempre van a ir por parejas. c cc%8%c"c$'#c Hasta ahora todos nuestros programas han dado los resultados en la pantalla usando letras de color gris claro sobre fondo negro. Para llorar, pero esto va a cambiar ahora mismo. El interfaz en modo texto nos ofrece la posibilidad de usar dieciséis colores de letra y ocho de fondo. No es que sea una maravilla tecnológica comparada con los millones de colores que usan los entornos gráficos actuales, pero para cumplir nuestros objetivos son más que suficientes. Vamos a ver la paleta de colores predeterminada del modo de pantalla VGA: Como se puede ver, cada color lleva un asociado un númerito. Este número se conoce como "Atributo de color" y nos va a servir para especificar los colores mediante la siguiente instrucción: COLOR primerPlano, fondo Por ejemplo, si queremos escribir con letras amarillas sobre fondo azul (muy típico), no tenemos más que usar una instrucción COLOR 14, 1 antes de las correspondientes instrucciones PRINT o INPUT. Al usar una instrucción COLOR, se cambian los colores de la pantalla para sucesivas instrucciones de escritura en pantalla hasta que se vuelva a cambiar usando otra instrucción COLOR. No hace falta poner una instrucción COLOR delante de cada PRINT o INPUT si no vamos a cambiar el color. Como color de primer plano podemos usar cualquiera de los 16 atributos de color disponibles. Como color de fondo solo los 8 primeros. Si no especificamos color de fondo, se conservará el que hubiera. El color de fondo en principio sólo afecta al trozo de pantalla que hay por detrás de los caracteres que escribimos. Si queremos colorear toda la pantalla de un determinado color, una forma muy rápida de hacerlo es especificar una instrucción COLOR justo antes de una CLS, por ejemplo: COLOR 15, 4 CLS Hará que toda la pantalla se coloree de rojo oscuro. En sucesivas instrucciones PRINT se escribirá lo que sea en color blanco fuerte sobre este rojo mientras no usemos otra instrucción COLOR. Hay que tener cuidado de no usar el mismo color para primer plano y para fondo. Si lo hacemos no pasa nada, nuestro programa va a seguir funcionando perfectamente, pero sería como escribir con lápiz blanco en un papel, no se vería nada de nada.
c c
También podemos conseguir que los caracteres aparezcan en la pantalla parpadeando. Para hacerlo no hay más que sumar 16 al color de primer plano que queramos, por ejemplo: COLOR 30, 2 Hará que el texto escrito posteriormente aparezca de amarillo parpadeando sobre fondo verde. (14 del amarillo más 16 son 30). No se puede hacer que el fondo parpadee. Una cosa importante a tener en cuenta con el parpadeo es que NO VA A FUNCIONAR si estamos ejecutando QBasic desde Windows en una ventana. Para que funcione habrá que pasar a pantalla completa pulsando ALT+ENTER o el botón correspondiente de la barra de botones de la ventana, y aún así a la primera tampoco funciona algunas veces, lo que se hace es afectar al color de fondo. No es conveniente abusar del parpadeo, puede resultar molesto para la vista. Se debe de utilizar sólo para resaltar pequeños mensajes o simbolitos en la pantalla. Como ejemplo veamos como podría quedar una portada sencilla en colores para un programa de agenda que integre todo lo que vimos en el tema de ficheros.
Para conseguir esto no habría mas que insertar las instrucciones COLOR correspondientes en los lugares adecuados, como se ve aquí: CLS PRINT COLOR 4 PRINT , , "* * * * * * * * * * * *" PRINT , , "* "; COLOR 10: PRINT "AGENDA SUPER BARATA "; COLOR 4: PRINT "*" PRINT , , "* *" PRINT , , "*"; COLOR 2: PRINT " J.M.G.B. Ronda 2003 "; COLOR 4: PRINT "*" PRINT , , "* * * * * * * * * * * *" PRINT PRINT
c c
COLOR 13 PRINT , "A ... Añadir nueva persona" PRINT , "B ... Borrar persona" PRINT , "M ... Modificar persona" PRINT COLOR 5 PRINT , "N ... Buscar persona por nombre" PRINT , "D ... Buscar persona por dirección" PRINT , "T ... Buscar persona por teléfono" PRINT , "E ... Buscar persona por edad" PRINT COLOR 1 PRINT , "L ... Ver listado de personas" PRINT COLOR 9 PRINT , "C ... Compactar base de datos" PRINT COLOR 11 PRINT , "S ... Salir" Estas son las instrucciones sólo para dibujar la pantalla. El resto para que funcione el menú habría que progrmarlo por ejemplo con la función INKEY$ y un SELECT CASE que según la tecla pulsada llame a cada uno de los módulos del programa. cc&4"c$'#c En el apartado anterior hemos visto cómo conseguir que nuestros programas escriban en la pantalla usando distintos colores. Esto, junto con los semigráficos que veremos en el apartado siguiente, es más que suficiente para hacer que nuestros programas tengan una interfaz en modo texto bastante conseguida. Observando la paleta VGA de 16 colores se puede comprobar que los colores están un poco "descoloridos". No es que al monitor le pase nada cuando entramos a MS-DOS, es que son los que hay y están definidos así. También nos puede pasar que queramos usar determinados colores, por ejemplo para construir un logotipo, y no nos venga bien ninguno de los que tenemos. Esto se puede arreglar un poco. El modo de pantalla de texto nos permite usar 16 colores SIMULTÁNEAMENTE, pero estos no tienen porqué ser siempre los mismos. Los podemos cambiar fácilmente por cualquier otro de esta paleta más amplia de 64 colores.
c c
Podemos intercambiar cualquiera de los 16 atributos de color que tenemos disponibles por uno de estos 64 colores usando la siguiente instrucción: PALETTE original, nuevo Vamos a ver lo que significa esto. Puede dar lugar a confusiones: *c Original es el ATRIBUTO de color que queremos cambiar (Un número entre 0 y 15) *c Nuevo es el color de esta paleta que queremos usar (Un número entre 0 y 63) Supongamos que queremos cambiar el atributo 3 (Que originalmente es un azul grisaceo muy feo) por un celeste más bonito que aparece en nuestra paleta con el índice 43. No habría más que hacer: PALETTE 3, 43 Y a partir de ahora cada vez que usemos la instrucción COLOR 3 se escribirá con nuestro celeste. El 3 ya no es el azul grisaceo feo. Podemos intercambiar los colores todas las veces que queramos e incluso asignar valores repetidos a distintos atributos. Lo que hay que tener en cuenta es que sólo vamos a ver en la pantalla 16 colores a la vez, pero estos podrán ser cualquiera de los 64. Otra cosa que hay que tener en cuenta es que los cambios hechos con la instrucción PALETTE también afectan a todo lo que ya haya escrito en la pantalla del color que hemos cambiado. Imagina que hay escrito algo en color 10 original (Verde fuerte), y ahora asignamos al atributo 10 el color de paleta 51 para seguir escribiendo en verde más suave. Automáticamente todo lo escrito en verde fuerte pasa a ser verde suave. Para poder ver los dos verdes a la vez habría que usar otro atributo y dejar el 10 como estaba. Con un poco de práctica haciendo esto se puede conseguir que determinados textos se enciendan o se apaguen poco a poco (Cambiando los colores por otros cada vez más oscuros o más claros dentro de un bucle), algunos efectos interesantes como ocultar el rótulo "Presione cualquier tecla y continúe". La instrucción PALETTE no tiene ningún interés para aprender a programar, pero nos puede servir para dar a nuestros programas un toque personal o extraño al utilizar combinaciones de colores que se salen de los 16 tan típicos de la amplia mayoría de programas de MSDOS. Hay que recordar que como fondo sólo podemos usar los ocho atributos primeros (0 a 7), pero ya podemos conseguir cosas como escribir sobre fondo amarillo o blanco. Pero con cuidado!, que no haya que usar gafas de sol delante del ordenador. c /cc$!!$%#c# +4$#c Ya sabemos escribir en colores. Con un poco de idea ya podemos dibujar recuadros aplicando distintos colores de fondo a distintas zonas de la pantalla, pero todavía podemos llegar un poco más allá. Podemos dibujar recuadros con bordes y líneas horizontales, verticales y esquinas usando unos caracteres especiales que nos
c c
permiten "ensamblar" las líneas en la pantalla como si se tratara de un puzzle: Los caracteres SEMIGRÁFICOS, casi gráficos. Estos caracteres no los tenemos en el teclado, y por lo tanto para conseguirlos habrá que recurrir a sus códigos ASCII, que son los siguientes:
Recordemos que para introducir un código ASCII hay que pulsar simultáneamente la tecla alternativa (ALT) y el código correspondiente en el bloque numérico situado a la derecha del teclado. Para demostrar la utilidad de los caracteres semigráficos vamos a crear una nueva portada para nuestro programa de agenda. Va a ser esta, que usa también colores personalizados.
Como se puede ver, bastante conseguida. Ya no tiene casi nada que envidiarle a la interfaz de un programa comercial de MS-DOS. Lo que hace falta es que todo lo demás también funcione perfectamente y sea útil. Ahora vamos con el listado del programa necesario para dibujar esto: PALETTE 0, 32 PALETTE 3, 11 PALETTE 4, 8 PALETTE 5, 17 PALETTE 7, 63 PALETTE 10, 4 PALETTE 11, 52 PALETTE 12, 36 PALETTE 13, 38 PALETTE 14, 54
c c
COLOR 15, 3 CLS COLOR 15, 4 LOCATE 2, 11: PRINT ""; STRING$(57, 205); "" LOCATE 3, 11: PRINT "|"; SPACE$(57); "|" LOCATE 4, 11: PRINT ""; STRING$(57, 205); "" COLOR 14, 4: LOCATE 3, 14 PRINT "A G E N D A S U P E R B A R A T A" COLOR 1, 7 LOCATE 7, 20: PRINT "--------------------------------------" LOCATE 8, 20: PRINT "| A ... |" LOCATE 9, 20: PRINT "| B ... |" LOCATE 10, 20: PRINT "| M ... |" LOCATE 11, 20: PRINT "--------------------------------------" LOCATE 12, 20: PRINT "| N ... |" LOCATE 13, 20: PRINT "| D ... |" LOCATE 14, 20: PRINT "| T ... |" LOCATE 15, 20: PRINT "| E ... |" LOCATE 16, 20: PRINT "--------------------------------------" LOCATE 17, 20: PRINT "| L ... |" LOCATE 18, 20: PRINT "--------------------------------------" LOCATE 19, 20: PRINT "| C ... |" LOCATE 20, 20: PRINT "--------------------------------------" LOCATE 21, 20: PRINT "| S ... |" LOCATE 22, 20: PRINT "--------------------------------------" COLOR 0, 7 LOCATE 8, 29: PRINT "Añadir nueva persona" LOCATE 9, 29: PRINT "Borrar persona" LOCATE 10, 29: PRINT "Modificar persona" COLOR 10 LOCATE 12, 29: PRINT "Buscar persona por nombre" LOCATE 13, 29: PRINT "Buscar persona por dirección" LOCATE 14, 29: PRINT "Buscar persona por teléfono" LOCATE 15, 29: PRINT "Buscar persona por edad"
c c
COLOR 12: LOCATE 17, 29: PRINT "Ver listado de personas" COLOR 11: LOCATE 19, 29: PRINT "Compactar base de datos" COLOR 13: LOCATE 21, 29: PRINT "Salir" COLOR 14, 5: LOCATE 25, 1: PRINT SPACE$(80); LOCATE 25, 10: PRINT "Curso de Programaci¢n"; COLOR 13: LOCATE 25, 46: PRINT "J.M.G.B. HECHO EN RONDA"; SLEEP ¿A que asusta? Como se puede ver, ya es un listado bastante largo. Antes de seguir hay que aclarar un pequeño problema que tiene: En Windows no podemos representar los caracteres semigráficos y los he sustituido por guiones, tuberías y escuadras para tener una idea de dónde va cada cosa. Al copiar este listado a QBasic tendrás que sustituirlos por los correspondientes semigráficos usando la tabla de códigos ASCII. Los del recuadro superior son de línea doble y los de abajo sencillos. Para empezar a desliar este listado de código tan largo hay que tener en cuenta que es un esquema secuencial. No hay bucles ni operaciones complejas. Así ya se ve bastante más sencillo. Si te lías con los colores redefinidos quita las instrucciones PALETTE y usa solo los colores normales. Así de paso te vas aprendiendo sus 16 códigos, también te servirán para algunos otros lenguajes de programación. Cuando esté terminado redefine los colores que quieras. Además de los caracteres semigráficos sencillos y dobles que hemos visto aquí, existen otros conocidos como "mixtos" que se pueden usar en las esquinas y en las uniones de líneas horizontales y verticales para ensamblar líneas sencillas y dobles, es lo que se ve en las esquinas y uniones de las líneas que forman este dibujo.
Esto podría resultar interesante, lo que ocurre es que estos caracteres semigráficos mixtos no son muy comunes y no aparecen en todas las tablas de caracteres ASCII. En el caso de que el usuario de nuestro programa esté utilizando una de estas tablas de caracteres extraña, no podrá ver los caracteres estos y nuestro diseño de pantalla quedará roto. Normalmente las esquinas de los recuadros se cambiarán por vocales acentuadas y otros símbolos extraños, algo así como esto:
Como no nos merece la pena correr el riesgo de que nuestros diseños de pantalla queden así de estropeados, lo mejor es evitar el uso de los semigráficos mixtos. Aunque los veamos en nuestra tabla de caracteres ASCII, puede que el usuario del programa no los tenga y
"c c
no pueda verlo correctamente. Lo mismo ocurre al imprimir si la impresora utiliza una tabla de caracteres ASCII distinta. Recordar que estamos en MS-DOS y no hay fuentes True Type ni nada parecido. Con un poco de imaginación se pueden construir diseños de pantallas más que aceptables usando sólo los semigráficos simples y dobles que ya hemos visto junto con los caracteres de relleno que veremos en el apartado siguiente. Para terminar con los semigráficos vamos a ver un pequeño algoritmo que usado como un procedimiento nos sirva para dibujar un recuadro en las posiciones de pantalla que nos interesen, algo muy útil.
Lo que hace es sencillo. Dibuja un cuadro con el borde de línea doble con la esquina superior derecha en la posición v,h y la anchura y la altura la obtiene de las variables largo y alto. La primera línea dibuja la parte superior, el bucle FOR dibuja la zona intermedia rellenando lo de dentro con espacios, y la última línea dibuja el borde inferior. Ponemos un punto y coma al final de cada línea para que si dibujamos el recuadro en la parte baja de la pantalla no se produzca el desplazamiento automático y se nos estropee todo el diseño de pantalla. Este ejemplo se podría ampliar especificando los colores en la llamada a la función o escribiendo algún tipo de barra de título como si fuera una ventana de las del editor de QBasic. =cc$!!$%#c#$!'#c&c''"c Además de los semi gráficos también existen otros caracteres que nos van a ser muy útiles para de diseñar las pantallas de nuestros programas en modo texto. Son los caracteres de relleno, aquí están sus códigos ASCII, porque tampoco los tenemos en el teclado:
Estos caracteres nos van a permitir rellenar zonas enteras de la pantalla para evitar que se vean todo del mismo color. Los más utilizados son los tres primeros que nos ofrecen distinta densidad de puntos. El cuarto rellena todo con el color de primer plano, y los dos últimos se usan sólo para cosas como dar efectos de sombras a recuadros y poco más. Para usar estos caracteres se suele recurrir a un bucle FOR que recorre la pantalla por filas y dentro dibuja series del carácter correspondiente usando la función STRING$. Vamos con un ejemplo que va a rellenar con el carácter 177 la mitad izquierda de la pantalla. FOR v = 1 TO 25
#c c
LOCATE v,1: PRINT STRING(40,177); NEXT Una cosa que aparece al usar grandes cantidades de estos caracteres es el llamado "efecto Moiré" (Muaré), consistente en una serie de lineas curvas que aparecen dibujadas en la pantalla. Se produce por una interferencia entre los puntos que forman estos caracteres de relleno y los píxeles físicos del monitor. Si nuestro monitor nos da ese problema, no se puede evitar, pero sí atenuar usando colores de primer plano y de fondo que no sean demasiado distintos. Ahora vamos con otro diseño más elaborado que nos ofrece un diseño de fondo de este tipo:
FOR v = 1 TO 25 FOR h = 1 TO 79 STEP 2 LOCATE v, h: PRINT STRING$(2, INT(RND * 3) + 176); NEXT NEXT Aquí se elige aleatoriamente uno de los tres caracteres disponibles en el momento de dibujarlo. Se han agrupado los caracteres de dos en dos para que salgan cuadros más o menos cuadrados. El siguiente ejemplo es parecido, pero esta vez los cuadros aparecerán ordenados ya que se usa un contador en vez de los números aleatorios. c = 0 FOR v = 1 TO 25 FOR h = 1 TO 79 STEP 2 c = c + 1 IF c = 3 THEN c = 0 LOCATE v, h: PRINT STRING$(2, 176 + c); NEXT NEXT El uso de estos caracteres de relleno junto con los recuadros de colores hechos a base de semi gráficos es una buena forma de dar a nuestros programas en modo texto una apariencia bastante aceptable.
$c c
c
Y ahora vamos a ver como escribirlos. (Solo en QBasic y en los programas de MS-DOS. En Windows no se puede) Hay dos formas. La primera sería usar la función CHR$, por ejemplo para dibujar un corazón bastaría con poner PRINT CHR$(4). La segunda, más cómoda es pulsar la combinación de teclas CONTROL + P y después, a continuación ALTERNATIVA + el código en el teclado numérico, por ejemplo para dibujar el corazón habría que pulsar CTRL+P y ALT+4 con lo que este símbolo quedaría directamente dibujado en la pantalla. Esto de CTRL+P nos vale para dibujar estos caracteres especiales en la mayoría de programas de MS-DOS así como en el propio intérprete de comandos (Símbolo C:\>). Hasta aquí muy bien. Ya podemos dibujar caras, flechas y otros símbolos en nuestras pantallas, pero no podemos olvidar que estos caracteres son especiales y hay que tener unas ciertas precauciones. La primera es que si un símbolo nos da algun problema como que se nos descoloquen los demás, pues directamente evitar usarlo y nada más. Otras precaucione son no usar los símbolos que no llevan asociado ningún carácter como son el de código ASCII 0 y tener en cuenta que cada vez que nuestro programa dibuje un ASCII 7 se oirá un pitido en el altavoz interno o bien sonará el sonido predeterminado de Windows.
c c
Estos caracteres los usaremos exclusivamente en instrucciones PRINT y no deberemos almacenarlos en ficheros, especialmente en ficheros secuenciales. Si los intentaremos imprimir van a provocar fallos en la impresora. De hecho si intentas imprimir tal cual está la tabla de caracteres ASCII que viene en la ayuda de QBasic y que incluyes estos símbolos observarás cosas como alguna línea se rompe o incluso que se hace un salto de página y un trozo de la tabla se imprime en la siguiente hoja. Esto se debe a que se han encontrado caracteres de saltos de línea y de página que la impresora ha interpretado como lo que son. También hay que tener precaución si estos caracteres aparecen de forma literal en las instrucciones del código fuente del programa. Si imprimes dicho código puedes encontrarte con problemas similares cada vez que salga uno de estos caracteres. Por lo tanto si piensas imprimir el código fuente del programa utiliza funciones CHR$ en lugar de los caracteres literales. También hay que saber que estos caracteres no se pueden ver en Windows, por lo que si abres el fichero BAS del código de tu programa en un editor de Windows como por ejemplo el bloc de notas, directamente no los verás o los verás marcados como un cuadro negro. c @cc#+'#cIc!#$!%c Para enriquecer un poco más las pantallas de nuestros programas en modo texto podemos usar la forma que tienen los propios caracteres para hacer pequeños dibujos. Lo más sencillo de esto es lo que se conoce como esmáilis (Correctamente se escribe smiley) y que estamos acostumbrados a utilizar en los foros y chats de Internet. Algunos de los más comunes y de significado conocido son los siguientes: :-) :-( :-D :-o X-D Se pueden encontrar listados muy extensos llenos de estos símbolitos junto con su significado, pero de todas formas puede que estos esmáilis sean muy poca cosa para decorar nuestros programas. Por eso podemos recurrir a diseños más complicados conocidos como ASCII-ART. Vamos con unos ejemplos... __ / /\ / / \ / / /\ \ / / /\ \ \ / /_/__\ \ \ /________\ \ \ \___________\/ 1111111111111111111111111111 1110000001111111111000000111 1100000000011111100000000011 1100000000001111000000000011
c c
1100000000000110000000000011 1100000000000000000000000011 1110000000000000000000000111 1111000000000000000000001111 1111100000000000000000011111 1111111000000000000001111111 1111111110000000000111111111 1111111111000000001111111111 1111111111100000011111111111 1111111111110000111111111111 1111111111111001111111111111 1111111111111111111111111111
./ | _________________ / / / __________ //\_ /R / | (__________) ||.R `.________________________ / / | __________ ||`._.R~~~~~~~~~~~~~~~~~~~~~~~~` / \ \__(__________)__\\/ | `\ | | ___________________ | |_________________...-------RRR- - - =- - = - = `. /| | \- = = - -= - = - == - =| ( | | |= -= - = - = - = - =--= = - = =| \| |_________________/- = - -= =_- =_-=_- -=_==_=_= -| | | ```------...___________________.R |________| \ / _ | | ,,,,,,, /=\ ,-R `-, /\___________ (\\\\\\\||=| | | \/~~~~~~~~~~~` ^^^^^^^ \=/ `--------R ` Podemos encontrar montones de ellos o atrevernos nosotros mismos a dibujar los nuestros (Por ejemplo un logotipo). Desde el punto de vista de la programación lo único que hay que tener en cuenta es que hay que dibujarlos línea por línea usando una instrucción PRINT para cada una, y que en muchos casos no podemos olvidar incluir espacios a la izquierda de determinadas líneas para que el dibujo no se estropee. Por ejemplo, para dibujar la primera de las anteriores figuras habría que hacer...
c c
PRINT " __" PRINT " / /\" PRINT " / / \" PRINT " / / /\ \" PRINT " / / /\ \ \" PRINT " / /_/__\ \ \" PRINT "/________\ \ \" PRINT "\___________\/" Estos dibujos fueron concebidos inicialmente para ser impresos en impresoras antiguas que no soportaban la impresión de gráficos, por lo que en principio es suficiente con que sean en blanco y negro, pero también nos podemos molestar en ponerlos de varios colores dibujándolos con distintas instrucciones PRINT. También podemos construir grandes letras para dibujar los títulos y rótulos de nuestros programas, por ejemplo... _ ___ ___ ___ ___ ___ ___ _ _ ___ _ _ ___ __ __ _ _ /\/| ___ /_\ | _ )/ __| \| __| __/ __| || |_ _| | | |/ / | | \/ | \| ||/\/ / _ \ / _ \| _ \ (__| |) | _|| _| (_ | __ || | || | R <| |__| |\/| | .` | \| | (_) | /_/ \_\___/\___|___/|___|_| \___|_||_|___\__/|_|\_\____|_| |_|_|\_|_|\_|\___/ ___ ___ ___ ___ _____ _ ___ ____ ____ ____ ______ | _ \/ _ \| _ \/ __|_ _| | | \ \ / /\ \ / /\ \/ /\ \ / /_ / | _/ (_) | /\__ \ | | | |_| |\ V / \ \/\/ / > < \ V / / / |_| \__\_\_|_\|___/ |_| \___/ \_/ \_/\_/ /_/\_\ |_| /___| Estas letras están construidas usando principalmente barras y símbolos de subrayado. También es común construirlas usando los caracteres semigráficos o el símbolo # repetido (Esto último es lo que hace la instrucción banner de Linux). Para dibujar un rótulo con las letras anteriores podemos hacerlo de forma literal con cuatro instrucciones PRINT (Una por cada línea) y los trozos de letras entre comillas... PRINT " _ _ ___ ___ _ _ ___ ___ _ _ ___ ___ _ _ ___ _" PRINT "| || || __|/ __|| || |/ _ \ | __|| \| | | _ \/ _ \| \| || \ /_\" PRINT "| __ || _|| (__|| __ | (_) | | _||| .` | | / (_) | .` || |) |/ _ \" PRINT "|_||_||___|\___||_||_|\___/ |___||_|\_| \_|_\\___/|_|\_||___//_/ \_\"
c c
Pero ya que estamos programando, lo más cómodo sería tener un procedimiento que nos dibujara el rótulo que queramos en la posición que le digamos solo con hacer una llamada del tipo... DibujaRotulo "Hecho en Ronda", 5, 10 Habría que construir un programa con muchas instrucciones Mid$ que vaya construyendo las letras línea por línea. Se puede encontrar en la sección de programas terminados de esta web.
c | c c c *c *c *c
/ xcc-c ccJ c J / cc'ccc cc / ccccc J
c / xcc"%&($$2"c!c'#c"%4!$#c +4$#c Todos los resultados de los programas que hemos hecho hasta ahora en este tutorial de programación los hemos podido ver en la pantalla a través de lo que denominamos "Interfaz de texto", primero sólo en blanco y negro, y más adelante en colores. A partir de aquí vamos a tener la posibilidad de usar la pantalla como una matriz de puntos de colores (Píxeles) donde tendremos la podremos dibujar lo que queramos usando las instrucciones adecuadas. Dos conceptos muy importantes que hay que tener en cuenta a la hora de trabajar con interfaces gráficos son la profundidad de color y la resolución. La profundidad de color se refiere al número máximo de colores que vamos a poder presentar en la pantalla simultáneamente. La resolución es el número de píxeles que forman la pantalla, a mayor resolución mayor calidad de imagen. En los apartados siguientes veremos primero la forma de iniciar el modo de pantalla gráfica de QBasic, algo muy sencillo, y después cómo dibujar figuras en la pantalla para que hagan nuestros programas y juegos más vistosos. c / cc'#c&#c&c!"%!''!c"c !#$c Olvidémonos un momento del tutorial de programación. Imaginemos que estamos trabajando con Windows, un interfaz gráfico muy moderno. Nuestra pantalla estará trabajando seguramente con una resolución de 800 por 600 pixeles y una profundidad de color de 16.7 millones de colores. De esta forma, además de las letras que aparecen por ejemplo en las barras de título de las ventanas, podemos ver dibujados en la pantalla los bordes de las propias ventanas, los iconos, los botones, y hasta una imagen de fondo en el escritorio que posiblemente sea una fotografía digitalizada con muchos colores. En medio de todo este despliegue de color pulsamos sobre un icono que nos va a lanzar un juego que tenemos instalado en nuestro ordenador. Este juego al empezar a funcionar nos cambia la resolución a 320 por 200 puntos y la profundidad de color a 256
c c
colores. Podremos ver que como la resolución ha descendido, los bordes de ciertas figuras redondeadas aparecen "pixelados", es decir, formando escalones. Podemos ver mejor los pixeles porque ahora son más grandes. También al reducir el número de colores las imágenes ya tienen un aspecto "como de dibujos animados". Esto es suficiente para un juego sencillo en la mayoría de los casos, pero para ver una fotografía digital como la que teníamos en el fondo del escritorio ya nos vendrían cortos. En medio de nuestra interesante partida con el juego, el ordenador se bloquea (algo muy común en Windows) y nos vemos obligados a reiniciarlo. Tras el arranque aparece un mensaje que nos indica que Windows va a funcionar en "modo a prueba de fallos". Una de las cosas que habrán cambiado será la resolución, que se ha reducido a 640 por 480 pixels y el número de colores que se ha quedado en 16. Podremos ver como la imagen del fondo del escritorio se ve muy fea y los iconos se ven algo más gordos que antes y con unos colores un poco extraños. Al abrir alguna ventana veremos que todo aparece un poco más grande porque hay menos resolución. Ya volviendo al curso de programación, vamos a ver ahora cómo conseguir que nuestros programas de QBasic trabajen con más o menos resolución y más o menos profundidad de color según nos convenga en cada caso. Ambos valores no los podemos cambiar de forma independiente, sino que van relacionados en lo que se conoce como "Modos de pantalla". Estos son los más usados: &c
#"%!$2"c
%8%c
#'($2"c
4("&&!&c &c$'c
#$"c ;c
J Fcc :c
@;c:c/c c
c
x=cc
#$"c xc
J Fc Jc
@;c:c;c c
=;c:c@;c :c
x=cc
#$"c xc
J Fc Jc
;c:c/c c
;c:c;;c :c
/=cc
Además de estos tres, hay otros más, pero han quedado en desuso ya que son monocromáticos (Blanco y negro) o bien de cuatro colores. Se usaban en los ordenadores más antiguos y en los actuales es posible que ya no lleguen ni a funcionar (Producirán un error de tiempo de ejecución al intentar iniciarlos). También existen otros modos con más resolución y con más colores, pero QBasic no los soporta. Para aprender a programar, que es lo que estamos haciendo aquí, estos tres son suficientes.
c c
En cada caso seremos nosotros los responsables de seleccionar cual es el mejor para nuestro programa, conociendo sus ventajas e inconvenientes. El modo 0 es el interfaz de texto que hemos venido usando hasta ahora, es el que QBasic usa por defecto si no le decimos que use otro. Si no tenemos necesidad de dibujar figuras gráficas, es el más indicado en la mayoría de los casos ya que es muy rápido. El modo 12 es el de más alta resolución que tenemos en QBasic, nos servirá para los casos en que tengamos que dibujar diagramas o figuras más complejas con muchas líneas. Su principal inconveniente es que es bastante más lento que los otros porque el ordenador tiene que manejar muchos puntitos para dibujar la pantalla. Los colores para el modo 12 son los mismos que la paleta básica normal del interfaz de texto, es decir, estos.
Hay que tener en cuenta que en el modo 12 no disponemos de los colores intermitentes como teníamos en la pantalla de texto, y que para escribir letras tenemos ciertas limitaciones que veremos en apartados posteriores. El modo 13 es de muy baja resolución, por lo tanto las imágenes se verán muy "pixeladas" y su tamaño en puntos no podrá ser muy grande ya que "no cabrán en la pantalla". Como ventaja principal tiene la posibilidad de usar una paleta de 256 colores, algo muy valioso para los juegos, y además es bastante rápido, ya que al haber 256 colores cada pixel de la pantalla se corresponde exactamente con un byte en la memoria gráfica, y las operaciones que tiene que hacer el ordenador para dibujarla son más sencillas. Este modo de pantalla se ha usado durante años para la programación de montones de juegos para MS-DOS que todavía hoy nos ofrecen una calidad gráfica más que aceptable. Para el modo 13 los colores son los de la siguiente paleta:
c c
Como se puede ver, los primeros 16 son iguales a los VGA, a continuación una escala de grises, y los siguientes van agrupados de 24 en 24, cosa que se puede utilizar para hacer efectos como degradados o sombras. Igualmente en el modo 13 tampoco hay colores intermitentes. El cambio de modo de pantalla en QBasic es extremadamente sencillo, basta con usar la siguiente instrucción: SCREEN modo Dónde modo es uno de los numeritos que hemos visto antes: 0, 12 o 13. El efecto que tiene esta instrucción es el borrado completo de la pantalla y que a partir de ahora podremos escribir y dibujar adaptándonos a la nueva resolución y a los colores que tenemos. Normalmente esto lo hacemos una vez nada más al principio del programa (si es para modo texto no hace falta) y ya no lo cambiamos más, aunque si hace falta se puede hacer todas las veces que sea necesario. En los siguientes apartados vamos a ver cómo se pueden dibujar figuras en la pantalla gráfica una vez que la hayamos activado usando la instrucción SCREEN. Un efecto que notaremos si tenemos un monitor moderno digital será que se apague y no volvamos a ver imagen alguna hasta pasado un instante. Lo mismo ocurrirá cuando termine un programa en modo gráfico y volvamos al editor de QBasic que es en modo texto. Si estamos trabajando con QBAsic en una ventana de Windows, para entrar a modos gráficos se pasa obligatoriamente a modo de pantalla completa, y cuando termine el programa y volvamos al editor de QBasic posiblemente lo encontremos también a pantalla completa. Para solucionar este problema consulta el :c de este totorial.
"c c
/ cc"&"%c"c&c +4$c Como podremos comprobar a lo largo de estos temas dedicados a los gráficos, el rendimiento de estas operaciones es bastante bajo independientemente de que tengamos un ordenador súper moderno o la mejor tarjeta gráfica del mercado con una gran cantidad de memoria. Este bajo rendimiento se debe principalmente a que QBasic es un lenguaje interpretado y a que las instrucciones no están optimizadas para alcanzar gran velocidad. Todas las instrucciones se reducen a dibujar puntos en la pantalla y QBasic no es capaz de dibujarlos lo suficientemente rápido. Los modernos videojuegos en tres dimensiones y con alta resolución, millones de colores, efectos de iluminación transparencias, reflejos, sombras... se reducen a eso, a dibujar en la pantalla puntos de colores. Los programas calculan cual es el color que tiene que tener cada punto y estos se colocan a gran velocidad como valores numéricos en la memoria gráfica del ordenador. El adaptador gráfico va leyendo esa información para dibujar la pantalla entera muchas veces por segundo, mientras más mejor. Los programas y las bibliotecas gráficas que permiten hacer eso (openGL, directX...) están escritas directamente en lenguaje ensamblador y depuradas para que todo se haga de la forma más rápida posible. También los controladores de nuestro sistema gráfico están escritos de forma que trabajan de forma sincronizada con estos programas para obtener el mayor rendimiento, mientras que QBasic utiliza el modo de pantalla VGA que se usa desde hace años y no aprovecha para nada estas tecnologías. No sabe ni que existen. Podemos depurar un poco nuestros programas y controlar cosas como que dentro de los bucles que se repiten mucho se ejecuten el menor número de instrucciones y cálculos matemáticos posibles, pero por muchas vueltas que le demos a nuestro código, en QBasic no podemos esperar obtener buenos resultados a una velocidad aceptable. Nos tendremos que conformar con sencillos dibujos que nos van a servir para aprender a programar y poder dar el salto a un lenguaje más avanzado que nos permita usar todas estas nuevas tecnologías.
| c c| c c c c *c *c *c *c *c *c
= = = = = =
xcccc J cc c-c#% cc'S cIc c-c'" cc$ScIc-. c-c$$' /cc c c-c!"% =cc&D cJ cD c-c&!X
#c c
= xcc'c$(#c +4$c Hay varias instrucciones específicas que nos permiten dibujar en la pantalla en modo gráfico. Antes de empezar a estudiar su utilización es importante darse cuenta de que en la pantalla en modo gráfico existe, además del cursor de texto, un cursor gráfico. Este sería comparable a "la punta de un lápiz que va dibujando cosas". Al iniciar uno de los modos gráficos con la instrucción SCREEN el cursor gráfico, siempre invisible, se sitúa en el centro de la pantalla, y posteriormente, conforme vayamos dibujando cosas con las instrucciones que vamos a ver a continuación, se irá moviendo para situarse siempre al final de la última figura que hayamos dibujado. Algo parecido a lo que ocurría con el texto, pero que es conveniente aclarar. c = cc("%# c"#%($$2"c#%c La primera instrucción que vamos a ver en modo gráfico nos va a permitir dibujar un punto en la pantalla en la posición que nosotros indiquemos, y del color que nosotros queramos. Su sintaxis es: PSET (h,v),color Los valores de h y v, siempre entre paréntesis y separados por una coma son las coordenadas horizontales y verticales del lugar en en el que vamos a dibujar el punto. Recordemos que la esquina superior izquierda de la pantalla tiene las coordenadas 0,0 y las de la esquina inferior derecha dependen del modo de pantalla elegido, en el modo 12 serán (639,479) y en el modo 13 serán (319,199). Observa que no son (640,480) o (320,200) porque, a diferencia de las coordenadas de texto, empezamos por cero y no por uno. Si nos salimos de la pantalla especificando coordenadas mayores o menores de los límites permitidos, no ocurre nada, simplemente que el punto se dibujará fuera de la pantalla y no lo veremos. El valor que elijamos para el color depende de la paleta asociada al modo de pantalla que tengamos activo: Si es el modo 12 podremos usar un valor entre 0 y 15 o si es el modo 13 tendremos disponibles los valores desde 0 a 255. Evidentemente, si dibujamos un punto negro sobre fondo negro no se verá. Si no especificamos un color se usará el de la anterior orden de dibujo, si era la primera orden se usará el color blanco. En la anterior sintaxis poníamos unas coordenadas absolutas, es decir, el cursor gráfico se movía hasta la posición indicada y se dibujaba el punto. También es posible indicar una posición relativa a la posición actual del cursor gráfico usando la palabra STEP antes del paréntesis de las coordenadas: PSET STEP(0,0), 14 Esta instrucción dibujará un punto amarillo en la posición actual del cursor gráfico, es decir, al final de la última figura dibujada o en el centro de la pantalla si es la primera instrucción de dibujo. Vamos con unos cuantos ejemplos más de esto de las posiciones relativas: SCREEN 13
$c c
PSET (200,100), 10 PSET STEP(0,40), 14 PSET STEP(60,0), 12 PSET STEP(-20,-20), 9 Este trozo de código activará el modo 13 y dibujará un punto verde exactamente en la posición (200,100), es decir, 200 pixels a la derecha y 100 pixels más abajo de la esquina superior izquierda de la pantalla. Seguidamente se dibujará un punto amarillo 40 pixels más abajo del anterior, uno rojo 60 pixels a la derecha del amarillo, y finalmente uno azul 20 pixels más arriba y 20 pixels a la izquierda del rojo. Como se puede ver, al usar coordenadas relativas, ya es útil usar valores negativos. Por supuesto, los valores de las coordenadas y de los colores no tienen porqué ser expresiones constantes, pueden ser el resultado de una expresión matemática con o sin variables, por ejemplo... PSET (inicio+largo, inicio+alto), colorActual O algo muy útil y muy normal es que las instrucciones de dibujo están dentro de bucles que las repitan. Normalmente no nos sirve de nada dibujar un punto solo, siempre dibujaremos muchos desde dentro de bucles, por ejemplo: SCREEN 12 FOR n=100 to 500 STEP 5 PSET (n,50),14 NEXT Esta instrucción dibuja en la pantalla una sucesión de puntos amarillos alineados. Si nos salimos no pasa nada, simplemente que se dibujarán fuera y no los veremos, pero si indicamos un color de fuera de la paleta del modo de pantalla actual se producirá un error de tiempo de ejecución y el programa se parará. = cc'6"!#c7c$%+" ('# c"#%($$2"c'"c La siguiente instrucción que vamos a ver nos va a permitir dibujar una línea recta en la pantalla. Su sintaxis es... LINE (h1,v1)-(h2,v2),color Aquí debemos especificar dos coordenadas: la del principio de la línea recta y la de su final. Tras dibujar la línea el cursor gráfico permanecerá en el final, es decir, en la posición de pantalla (h2,v2). Igual que en el caso anterior, podemos especificar coordenadas relativas usando la palabra STEP antes del paréntesis para una o las dos posiciones. Esto es útil para dibujar líneas poligonales, en este caso cada segmento se dibujaría con una instrucción LINE y todas menos la primera empezarían con LINE STEP(0,0) para que empiecen junto al final de la anterior, por ejemplo este código dibuja un triángulo: LINE (150,100)-(100,200),10 LINE STEP(0,0)-(200,200) LINE STEP(0,0)-(150,100)
c c
Si la última coordenada de la última instrucción es igual a la primera coordenada de la primera instrucción, tendremos una línea poligonal cerrada, en caso contrario quedará abierta. Si la línea que estamos dibujando es completamente horizontal o completamente vertical será dibujada toda continua, si es diagonal aparecerá formando como "Escalones". Este efecto es lo que se conoce como "pixelado" y será más visible en el modo 13 ya que al ser de menos resolución, los pixels son "más gordos". Esta instrucción LINE también nos permite dibujar un rectángulo (o un cuadrado) de forma muy sencilla, simplemente añadiendo una coma y una letra B a continuación del color, por ejemplo... LINE (20, 20)-(70, 30), 12 Dibujaría una línea recta diagonal que va desde (20,20) hasta (70,70). Y... LINE (20,20)-(70,30), 12, B dibujaría un rectángulo con una de sus esquinas en la posición (20,20) y la opuesta en la posición (70,70). El borde del rectángulo tendrá una anchura de un pixel y en este caso será de color amarillo. Si queremos que el rectángulo se dibuje relleno basta con poner las letras BF (Borde y fondo) en vez de la B sola, por ejemplo... LINE (20,20)-(70,30), 12, BF Si no queremos especificar el color, para que se coja el usado en la instrucción anterior, basta con no ponerlo, quedarán dos comas juntas. LINE (20,20)-(70,30), , BF Un poco extraño, pero cuidado con equivocarse, porque si ponemos... LINE (20,20)-(70,30), BF Se dibujará una línea recta entre las dos posiciones, y será del color que se almacene en una nueva variable llamada BF que seguramente será el cero que es negro y puede que no veamos. Como norma general es mejor poner el color siempre. Al usar la segunda coordenada relativa en los recuadros, sean rellenos o no, podemos calcular fácilmente su tamaño exacto sin tener que hacer cálculos, por ejemplo: LINE (20,20)-STEP(99,99), 14, BF Nos dibujaría un cuadrado amarillo relleno de 100 por 100 píxeles (99 + 1) con la esquina superior izquierda en la posición que indicamos en la primera coordenada sin tener que calcular nosotros donde iría la otra esquina. Hay que tener en cuenta que en el modo de pantalla 13 los cuadrados siempre los vamos a ver un poco más altos que anchos aunque hayamos puesto las longitudes de sus lados iguales, ya que la resolución de este modo de pantalla (320x200 píxels) no es proporcional a las dimensiones físicas del monitor que siempre guardan una proporción de 3:4. Para dibujar un cuadrado que se vea cuadrado de verdad habría que dibujarlo con una altura un poco menor que su anchura para que se disimule el error, pero tampoco no merece mucho la pena complicarse tanto en la mayoría de los casos.
c c
Por último, podemos conseguir que las líneas y los cuadros sin relleno (B) se dibujen de forma discontinua, es decir, unos puntos sí y otros no. Para hacer esto se pone al final del todo un numerito que nos indica el patrón de puntos a utilizar. Este valor es un número de 16 bits (entre 0 y 65.535) que lo podemos poner en hexadecimal para no liarnos comenzándolo con los caracteres &H. (entre &H0000 y &HFFFF) Vamos con unos ejemplos: LINE (20,20)-(70,30), 14,B ,&H5555 Se dibujaría un rectángulo amarillo con el borde punteado, si no queremos poner el color haríamos: LINE (20,20)-(70,30), ,B ,&H5555 Y si queremos que sea una línea y no un rectángulo no tendríamos que poner la letra B, pero sí su correspondiente coma, sería así: LINE (20,20)-(70,30), , ,&H5555 Esto no se utiliza mucho. Si queremos otro patrón de puntos distinto basta con ir probando números. Los más útiles pueden ser &H5555 que nos van dibujando "uno sí y otro no" y &HFF00 que nos harían un efecto parecido a la línea discontinua de una carretera. Si queremos una línea a dos colores se puede hacer dibujando una normal de un color y justo encima una discontinua del otro color, por ejemplo... LINE (100,100)-(300,200), 15 LINE (100,100)-(300,200), 12, ,&HFF00 Esto nos dibujaría una línea a trozos blancos y rojos. = cc$6$('#c7c23!'# c"#%($$2"c$$'c La siguiente instrucción nos va a servir para dibujar una circunferencia (sólo la línea del contorno) en nuestra pantalla gráfica. CIRCLE (h, v), radio, color En este caso se dibujará una circunferencia con el centro situado en la posición (h,v) y con el radio (medido en píxels) que indiquemos. Tras el trazado de la misma el cursor de gráficos permanecerá en el centro. Por ejemplo: CIRCLE (100, 200), 50, 12 Dibujará una circunferencia de color rojo con el centro en la posición (100,200) y con un radio de 50 pixels. Si no especificamos el color se tomará el de la anterior instrucción de dibujo, como en los casos anteriores. También tenemos la posibilidad de usar una coordenada relativa anteponiendo la palabra STEP al paréntesis, por ejemplo... LINE (40, 10)-(100,200),14 CIRCLE STEP(0,0),10 ...dibujaría una línea recta inclinada de color amarillo y en su extremo final una circunferencia del mismo color con un radio de 10 píxels. La instrucción CIRCLE también nos permite dibujar arcos de circunferencia, aunque esto no se usa mucho, no hay porqué dibujarla siempre entera. Para hacerlo hay que poner a continuación del color el ángulo inicial y final del arco que queremos dibujar. Los ángulos van en radianes, la circunferencia completa tiene unos 6.28 radianes (2 veces pi) y el ángulo 0 está la la derecha, el pi/2 radianes
c c
(90º) está arriba, el pi radianes (180º) está a la izquierda y el 3/2 pi radianes (270º) está abajo. De esta forma, por ejemplo, para dibujar la mitad superior de una circunferencia (desde 0 hasta pi radianes) habría que poner: CIRCLE (100, 100), 40, 14, 0, 3.14 Y si nos queremos ahorrar el color y que lo coja de la instrucción anterior, pues no lo ponemos. CIRCLE (100, 100), 40, , 0, 3.14 Por último, también podemos dibujar óvalos en vez de circunferencias. Para hacerlo añadimos otro parámetro más a continuación. Este parámetro será la proporción entre el radio horizontal y el vertical. Por ejemplo, si es dos, el radio vertical será del doble que el horizontal, y si es 0.5 el radio vertical será la mitad del horizontal y la circunferencia aparecerá aplastada. El radio que nosotros hayamos puesto en la instrucción será el mayor de los dos, es decir, el horizontal si este valor es menor que 1 o el vertical si nuestro valor de aspecto es mayor que uno. En este ejemplo dibujamos sólo la mitad izquierda de una circunferencia verde aplastada que sería cuatro veces más ancha que alta si estuviera entera:
CONST PI = 3.14; CIRCLE (200, 200), 160, 9, PI/2, 3*(PI/2),0.25 Si queremos dibujar el mismo óvalo entero (Algo menos rebuscado), no ponemos los valores de principio y fin del arco, pero sí tenemos que respetar sus posiciones poniendo las comas: CIRCLE (200, 200), 160, 9, , ,0.25 El dibujo de circunferencias no nos permite rellenarlas (Eso lo haremos en el apartado siguients), así como tampoco el trazado de una línea punteada. Las últimas cosas que hemos visto de los arcos y de los óvalos ya no serán aplicables en Visual Basic. En todo caso las circunferencias se dibujan muy pixeladas, especialmente si son de pequeño tamaño. Hay que tener en cuenta que en el modo de pantalla 13 las circunferencias no saldrán redondas, siempre un poco más altas que anchas, ya que su resolución (320x200 píxels) no es proporcional a las dimensiones físicas del monitor que siempre guardan una proporción de 3:4. c = /cc''"!c+!# c"#%($$2"c!"%c También es posible conseguir que una parte de nuestra pantalla se rellene de un color o de un patrón. Sería algo parecido a la
c c
herramienta "Cubo de pintura" que tenemos en la mayoría de programas de dibujo como es el caso del Paint de Windows, pero con alguna diferencia. El funcionamiento de la herramienta "Cubo de pintura" del Paint consiste en que al pulsar sobre una parte de la pantalla con esta herramienta, todos los píxels adyacentes del mismo color cambiarán al nuevo color. Se produce un efecto como que la "pintura" se va extendiendo, sólo que es muy rápido y no lo notamos. En QBasic la instrucción que tenemos para hacer esto es la siguiente: PAINT (h, v), colorrelleno, colorlímite Al ejecutar esta instrucción se produciría un efecto parecido a "Echar pintura del color colorrelleno en la posición (h, v) y esta se iría extendiendo por todos los sitios hasta encontrar píxeles del color colorlímite". Una gran diferencia con los programas de dibujo es que el color se va extendiendo hasta quedar encerrado por píxeles del colorlímite, independientemente de los otros colores que se encuentre por el camino, a los que cubrirá. Para especificar una posición relativa también podemos usar coordenadas relativas anteponiendo la palabra STEP como en casos anteriores. Vamos con un ejemplo que rellenaría un círculo, algo muy común: SCREEN 12 CIRCLE (100, 100), 40, 12 PAINT (100,100), 14, 12 Lo primero que hacemos es dibujar un círculo de color rojo (12) sobre el fondo de la pantalla que es negro porque no hay nada más dibujado. A continuación echamos pintura amarilla (14) dentro del círculo y le decimos que pare cuando encuentre píxeles rojos (12), con lo que la pintura no llegará a salirse del círculo. El color puede ser el mismo que el color límite, pero el color límite no puede ser el mismo que el color de fondo que tiene el sitio donde empezamos a pintar, ya que entonces solamente se dibujaría un punto. Esta instrucción puede dar lugar a que la "pintura" se salga y llene toda la pantalla, vamos con un ejemplo: SCREEN 12 CIRCLE (100, 100), 40, 12 LINE (120, 120)-STEP(50, 50), 9, B PAINT (100, 100), 10, 12 Primero dibujamos una circunferencia roja, después un cuadrado azul encima de la circunferencia, y al rellenar desde dentro de la circunferencia observaremos como el color verde "se sale" de la circunferencia y llena toda la pantalla, borrando también el recuadro azul. Algo así sería:
c c
Puedes observar fácilmente que el color verde se ha salido del círculo por los dos agujeros que quedan a la derecha y abajo. Estos se produjeron al dibujar el cuadrado azul, borrando algúnos píxeles rojos. Para evitar este problema no hay más que planificar cuidadosamente el orden en que se van dibujando los distintos objetos gráficos y las posiciones exactas donde usamos la instrucción PAINT. Por último decir que en vez de un color sólido podemos rellenar usando un patrón de relleno, en este caso habría que sustituir el color por una cadena de ocho caracteres que codificaría el estilo del patrón. Para ver más detalles consultar la ayuda en pantalla de QBasic. No profundizaremos más en el tema porque de todas formas la instrucción PAINT ya no podrá ser utilizada en Visual Basic. c = =cc& (5!c4 (!#c$'5!# c"#%($$2"c&!Xc En los apartados anteriores hemos visto como podemos dibujar figuras elementales como puntos, líneas, recuadros y circunferencias. Para dibujar figuras más complejas podemos optar entre usar una serie de instrucciones de estas o bien usar una sola de la que vamos a ver a continuación. DRAW CadenaComando$ Esta instrucción es completamente distinta a las anteriores. En principio no aparecen coordenadas y ¿Qué es eso de CadenaComando$...? Ahora lo vamos a ver. La instrucción DRAW lo que hace es mover el cursor de gráficos a partir de la posición actual donde se encuentre para ir dibujando lo que nosotros queramos. Y para saber lo que nosotros queremos que dibuje se utiliza esta CadenaComando$ que no es más que una expresión de cadena o una cadena de caracteres entre comillas que contiene algunos de los siguientes caracteres para que QBasic los interprete y sepa lo que tiene que dibujar. Estos son los caracteres que hay que poner para que el cursor gráfico se mueva en las distintas direcciones y vaya dibujando como si fuera la punta de un lápiz (Up, Down, Left, Right, E,F,G,H):
Con un ejemplo se ve más claro. Las letras pueden ser tanto mayúsculas como minúsculas. Pero se prefieren en minúsculas para dar más legibilidad.
c c
Imaginemos que queremos dibujar una escalera con cinco escalones que sube hacia la derecha. Basta con poner... DRAW "ururururur" Ya está, mira que sencillo. Si hubiéramos hecho esto con instrucciones LINE habría que haber puesto 10 instrucciones! Y haberse hartado de calcular coordenadas. Ahora vamos a ir añadiendo cosas... Si ejecutamos esto veremos que efectivamente se dibuja la escalera, pero muy pequeñita, ya que cada escalón solo mide de alto o de ancho un pixel. Para conseguir que los trazos sean más largos en la dirección que indiquemos no hay más que poner el número de píxeles a continuación de la letra. Haciendo lo siguiente conseguiremos que cada escalón mida tres píxeles de alto y cinco de ancho:
DRAW "u3r5u3r5u3r5u3r5u3r5" Vamos con otro ejemplo más elaborado que dibuja una flecha apuntando hacia la izquierda (Empezando por la punta):
DRAW "e6d3r6d6l6d3h6" Además de esto podemos conseguir cosas como que el cursor gráfico vuelva atrás tras haber hecho un trazo, por ejemplo para dibujar esta especie de estrella haríamos:
DRAW "nu6nd6nl6nr6ne6nf6ng6nh6" Hemos puesto una N delante de cada letra, de esta forma tras dibujar cada punta de la estrella el cursor vuelve al centro antes de dibujar la siguiente. También podemos hacer que el cursor se mueva sin dibujar, sería algo parecido a levantar el lápiz del papel y moverlo, por eso para dibujar una línea discontinua haríamos: DRAW "r6br6r6br6r6br6r6br6r6" Hemos puesto una letra B delante de los trazos que no queremos que se dibujen. Con esta forma de dibujar consegumos movel el cursor a saltitos a partir de la posición de la pantalla donde terminó el trazo anterior.
c c
También podemos hacer que salte directamente a una posición de la pantalla usando la letra M seguida de las coordenadas horizontal y vertical separadas por una coma. Por ejemplo para dibujar un triángulo usando esta técnica haríamos... DRAW "bm100,100m140,100m120,70m100,100" Al principio llevamos el cursor hasta la posición (100,100) sin dibujar (Poniendo la B delante) para que no se dibuje una línea desde la posición actual del cursor hasta el triángulo, cosa que no queremos. Después vamos saltando a los otros dos vértices del triángulo y finalmente volvemos hasta la posición inicial para que la figura quede cerrada. Observa que usando esta técnica ya podemos dibujar líneas diagonales que no estén necesariamente a 45 grados. Al usar la instrucción DRAW se utiliza el color de la anterior instrucción de dibujo, o el blanco si era la primera. También podemos especificarlo nosotros usando la letra C seguida del número del color que queramos igual que en las demás instrucciones que ya hemos visto. También como novedad podemos cambiar de color todas las veces que queramos dentro de la misma secuencia de comandos, de esta forma podremos dibujar un trozo de la figura de un color y otro de otro distinto. Como ejemplo de esto vamos a dibujar un recuadro con las líneas izquierda e inferior de verde (Color 10) y las líneas derecha y superior de azul (Color 9).
DRAW "c10d40r40 c9u40l40" También puedes observar en este ejemplo que se ha utilizado un espacio para separar un poco las "dos partes" del recuadro. Puedes utilizar todos los espacios que quieras para separar las distintas partes de un dibujo, especialmente si hay una tira de letras muy larga. Por último vamos a ver como hacer que las figuras dibujadas con la orden DRAW puedan estar rellenas de color. Sería algo parecido a la instrucción PAINT, pero integrada en la orden DRAW. Basta con llevar el cursor gráfico a una posición dentro de la figura (Con la letra B para que no se dibuje) y allí dentro usar la letra P seguida del color que queremos usar de relleno y del color de bordes, que tendrá que ser el que hemos utilizado para dibujar el contorno para que "la pintura no se salga". Como ejemplo vamos a dibujar un triángulo con el borde rojo y lo de dentro blanco...
"c c
DRAW "c12 bm100,100m140,100m120,70m100,100 bm110,95 p15,12" Empezamos por el vértice inferior izquierdo en la posición (100,100) y cuando volvemos a él para cerrar la figura hacemos saltar el cursor hasta dentro sin dibujar y rellenamos. Es importante llevar el cursor hasta el sitio interior sin dibujar ya que si lo hacemos dibujando se intentará rellenar a partir de un punto ya dibujado seguramente con el color de borde y no funcionará. La instrucción DRAW tiene todavía más posibilidades, pero como el uso de estas otras opciones es muy raro, todavía más que el de la propia DRAW, no las vamos a ver aquí. Para saber cuales son mira la ayuda en pantalla de QBasic. Esta instrucción es útil para dibujar figuras complejas, pero lo que aprendas de ella sólo te va a servir para QBasic. En Visual Basic ya desaparece y en otros lenguajes si existe algo parecido funciona de forma totalmente distinta.
| c c| c cc c *c *c *c *c *c
< < < < <
xcc&Jc cc. F - c-c3X cc&Jcc cc c F cc$ cIc cc %cIc(% cc!. ccccc: c4-c"% ccc:ccc J
c < xcc&4"c!$#c&c3#(!'[!$2" c"#%($$2"c 3Xc Un marco de visualización es un mecanismo que tiene QBasic para poder dibujar solamente en una parte de la pantalla sin miedo a meternos en otras partes ya dibujadas y estropear el trabajo ya hecho con instrucciones anteriores. Al definir un marco de visualización se define un rectángulo de manera que QBasic solo podrá dibujar dentro de él. El resto de la pantalla se queda como está y no podrá ser alterado hasta que se defina otro marco de visualización. Esta técnica no es muy utilizada y solo nos va a ser útil en casos muy determinados, pero ya que en la ayuda de QBasic se hace referencia constantemente a esto, vamos a verlo. Para definir el marco de visualización utilizaremos la siguiente instrucción, similar a LINE. VIEW (h1, v1)-(h2,v2) Así definimos un recuadro dentro del cual podremos dibujar sin miedo a salirnos e invadir otras partes de la pantalla. Vamos con un ejemplo:
#c c
SCREEN 12 VIEW (100, 100)-(400, 300) LINE(0, 0)-(500, 400), 14 Haciendo esto definimos un recuadro imaginario que abarca parte del centro de la pantalla. Después dibujamos una línea que va desde la esquina superior izquierda de nuestro recuadro (Las coordenadas ahora empiezan desde ahí) hasta donde llegue según las dimensiones que les hemos dado, pero en todo caso sólo se dibujará el trozo de línea que cae dentro de nuestro marco de visualización. El resto de la pantalla queda tal cual está. Si nos liamos con el nuevo origen de coordenadas y queremos seguir usando el normal con el origen en la esquina superior izquierda de la pantalla no hay más que poner la palabra clave SCREEN delante de las coordenadas del marco de visualización, por ejemplo: VIEW SCREEN(100, 100)-(400, 300) Así dejamos como estaba el origen de coordenadas, pero siempre se dibujarán solamente los trozos de figuras que entren en el marco de visualización. Al definir un marco de visualización, las figuras que hubiera ya dibujadas en esa zona se respetan, es como si el marco de visualización fuera transparente. En algunos casos nos puede interesar rellenarlo directamente de un color. Para hacerlo solo hay que poner el valor de color a continuación de las coordenadas, por ejemplo para que se rellene de azul sería: VIEW (100, 100)-(400, 300), 1 También podemos aprovechar para dibujar un borde alrededor del marco de visualización. Habría que poner a continuación el color que queremos para el borde, por ejemplo: VIEW (100, 100)-(400, 300), 1, 14 Así dibujaríamos el interior azul oscuro y un borde amarillo alrededor. Es importante entender que este borde va fuera del marco de visualización, y por lo tanto tampoco podrá ser alterado con las instrucciones de dibujo. Si no queremos relleno, para conservar lo que ya haya dibujado, pero sí queremos el borde, también se puede hacer. Habrá que respetar la posición del color de relleno, pero sin poner ningún número, aunque parezca extraño se pueden poner dos comas juntas: VIEW (100, 100)-(400, 300), , 14 Para usar la pantalla completa como marco de visualización es muy sencillo, simplemente: VIEW En esta instrucción no podemos usar la palabra clave STEP para indicar coordenadas relativas. c < cc##%!c&c$&"!&!#c#"!'[!&c Hasta ahora, para definir las posiciones donde si dibujaban las figuras usando las instrucciones de dibujo hemos usado un sistema de coordenadas en el que la coordenada superior derecha de la pantalla
$c c
(o del marco de visualización actual) era la (0, 0) y la de la esquina inferior izquierda dependía del modo de pantalla (podía ser (639, 479) para SCREEN 12 o (319, 199) para SCREEN 13). Este sistema es extremadamente cómodo porque cada unidad representa a un pixel de la pantalla, pero en algunos casos nos puede interesar definirnos otro sistema de coordenadas donde cada unidad represente a más o manos de un pixel. Supongamos que queremos que cada unidad represente a 64 pixeles en horizontal y a 48 en vertical. Sería algo como dividir la pantalla en una cuadrícula de 10 por 10 cuadros. Si estamos en SCREEN 12 habría que hacer... WINDOW (0, 0)-(10, 10) De esta forma la coordenada INFERIOR IZQUIERDA de nuestra pantalla pasa a llamarse (0,0) y la SUPERIOR DERECHA pasa a ser la (10,10) Es importarse darse cuenta que se ha cambiado superior por inferior, pero no izquierda por derecha. Hecho esto, si ejecutamos la instrucción... PSET (5, 5) ...se dibujará un punto nada más y nada menos que en el centro de la pantalla, que tiene las coordenadas (5,5) con nuestro nuevo sistema de coordenadas. Si nos liamos con el cambio de arriba por abajo es mejor usar la palabra clave SCREEN a continuación de WINDOW. De esta forma la primera coordenada corresponde a la esquina superior derecha y la segunda a la inferior izquierda, como hemos hecho hasta ahora. Sería, por ejemplo... WINDOW SCREEN (0, 0)-(10, 10) Para volver a dejar el sistema de coordenadas original de nuestro modo de pantalla bastaría con usar la instrucción WINDOW sin nada más. WINDOW Esto de los sistemas de coordenadas personalizados casi nunca nos será de utilidad, ya que es más cómodo trabajar directamente con el original de cada modo de pantalla que va relacionado directamente con el número de píxeles. De todas formas nos va a servir para entender mejor el concepto de TWIP que es la unidad que utiliza Visual Basic por defecto para trabajar con las coordenadas de los gráficos. De todas formas allí también es sencillo pasar a trabajar con píxeles directamente y olvidarse también de los TWIPS que no son más que un sistema de coordenadas personalizado definido por el propio Visual Basic. c < cc$!c7c ! c"#%($$"#c %c7c(%c En todos los programas de dibujo hay procedimientos para copiar un fragmento de imagen y pegarlo por la pantalla todas las veces que queramos. Nuestro lenguaje de programación no va a ser menos y nos permite, con algunas limitaciones, capturar con una instrucción
c c
un área rectangular de nuestra pantalla gráfica y después pegarla una o más veces por la pantalla usando otra instrucción. En Windows, por ejemplo, para todas las operaciones de copiar y pegar se utiliza una zona especial de la memoria llamada portapapeles que es controlada por el sistema operativo y nosotros únicamente nos tenemos que limitar a meter y sacar de allí todo lo que queramos. En nuestros programas de QBasic no hay portapapeles ni nada que se le parezca, y por lo tanto somos nosotros los encargados de "construir uno". Para hacerlo tendremos que declarar un vector de tipo entero de tamaño suficiente para almacenar temporalmente los fragmentos de imágenes que queremos copiar y pegar. La instrucción que usaremos para "copiar" un trozo de pantalla es esta... GET (h1, v1)-(h2, v2), nombreVector Esta instrucción tiene el mismo nombre que la utilizada para sacar información de los ficheros de registros de acceso directo o aleatorio, pero al usarla con esta sintaxis, ya QBasic se da cuenta que es para trabajar con gráficos. Como se puede ver, tiene una sintaxis muy parecida a la instrucción LINE. Lo que hace es coger el trozo de pantalla delimitado por el recuadro que va desde la coordenada (h1, v1) a (h2, v2) y almacenarlo en el vector que le pasemos, que deberemos haber declarado previamente y disponer de tamaño suficiente. El contenido de la pantalla no sufrirá ningún cambio. Esto parece muy sencillo, pero hay varias cosas que hay que tener en cuenta. Lo más importante es que el tamaño del vector debe ser suficiente. Para calcularlo exactamente se puede usar la fórmula que viene en la ayuda en pantalla de QBasic en el tema "Matrices para imágenes de pantalla y compatibilidad". Recordemos que en la ayuda de QBasic llaman matrices a todos los arrays, ya sean de una, dos o más dimensiones. En nuestro caso lo que nos interesa es un vector de enteros con una sola dimensión. Como esta fórmula está muy liada podemos hacernos una idea aproximada del tamaño del vector sabiendo que si en el modo de pantalla 13 hay 256 colores, cada pixel ocupa un byte. Bastará con calcular el área del cuadro que vamos a copiar (altura por anchura) y añadirle siempre unos 256 bytes más. En el modo 12, como sólo hay 16 colores, cada pixel ocupa en memoria 4 bits, que es un nibble o medio byte y por lo tanto el tamaño requerido para el vector será la mitad que en el caso anterior, siempre añadiremos algo más. Con un ejemplo que haremos más adelante esto quedará más claro. Ya hemos visto como "copiar" la imagen a "nuestro portapapeles". Ahora vamos a ver como "pegar" en la pantalla lo que acabamos de copiar. Habrá que utilizar otra instrucción distinta, que es esta... PUT (h, v), nombreVector, operacionPegado Esta instrucción , que también se llama como la de escribir en ficheros pero que nunca se va a confundir, lo que hace es dibujar en
c c
la pantalla, a partir de la posición (h, v) hacia la derecha y hacia abajo, el fragmento de imagen que hayamos almacenado previamente en el vector con una instrucción GET. Lo de operaciónPegado se refiere a una palabra clave que habrá que poner al final para decirle a QBasic como tiene que dibujar los puntos que componen nuestra imagen. Si esta palabra es AND, OR o XOR, los puntos de la imagen que estamos pegandose combinarán con lo que ya hubiera dibujado en esa parte de la pantalla según estas operaciones lógicas. Si la operaciónPegado es PSET los puntos se dibujarán de forma normal borrando en todo caso lo que hubiera detrás. Si usamos PRESET los puntos se dibujarán borrando lo que hubiera detrás, pero en este caso en vídeo inverso. Lo normal es utilizar siempre PSET. Si no lo ponemos, QBasic creerá que estamos queriendo utilizar XOR, cosa que nos puede confundir. Vamos con un ejemplo de como conseguir que un trozo de imagen que dibujemos en la pantalla en modo gráfico, usando por ejemplo la orden DRAW, se pueda copiar por toda la pantalla sin tener que repetir la correspondiente orden DRAW en cada caso.
Rdeclaramos un vector DIM portapapeles(1 TO 400) AS INTEGER R activamos la pantalla gráfica y dibujamos algo SCREEN 12 CIRCLE (10, 10), 8, 14 DRAW "c9bm4,10e6d3r6d6l6d3h6" Rcapturamos lo que haya dentro de un recuadro de 20x20 pixels GET (0, 0)-STEP(19, 19), portapapeles Rpegamos lo capturado tantas veces como queramos con PUT FOR h = 0 TO 620 STEP 20 FOR v = 0 TO 460 STEP 20 PUT (h, v), portapapeles, PSET NEXT NEXT Ry ya está Como se puede ver, el ejemplo se explica por sí solo, pero de todas formas vamos a verlo con un poco más de detalle y vamos a comentar algunas cosillas. El resultado de ejecutar este programa sería que dibujamos en la esquina superior izquierda de la pantalla una circunferencia amarilla y dentro una flecha azul. Esto lo copiamos con la orden GET y lo
c c
almacenamos en un vector llamado portapapeles que hemos declarado previamente con un tamaño de 400, suficiente, ya que la imagen que vamos a almacenar tiene un tamaño de 20x20puntos y cada punto ocupa en este modo de pantalla 4 bits. Después con PUT pegamos la imagen desde dentro de un bucle para que rellene toda la pantalla. Usamos la operación PSET para que no se combine con el dibujo original que sigue estando en la esquina superior izquierda. En este caso justo después de GET hemos usado PUT. Pero esto no tiene porqué ser así. Entre medio podemos borrar la pantalla, dibujar otras cosas o hacer lo que queramos, siempre que no modifiquemos los valores almacenados en nuestro vector. Incluso se podría cambiar de modo de pantalla y después volver al original. Hay que tener en cuenta que no podemos copiar algo en un modo de pantalla y después pegarlo en otro distinto que tenga una resolución horizontal distinta. También es importante darse cuenta que al dibujar con las instrucciones de dibujo que hemos visto anteriormente nos podíamos "salir" de la pantalla sin mayores consecuencias, pero al usar las instrucciones GET y PUT nunca nos podemos salir fuera, ya que se produciría un error de tiempo de ejecución y el programa se detendría. Para finalizar con estas dos instrucciones, decir que su principal limitación es que el máximo tamaño de los vectores que podemos declarar es de 64 KB (usando subíndices desde -32768 hasta 32767) y por lo tanto queda limitado el tamaño máximo de las imágenes que queremos copiar. De todas formas este tamaño máximo seguramente no lo podamos tampoco alcanzar porque las demás variables y el propio QBasic van gastando más memoria y nos dará un error de memoria agotada si intentamos declarar vectores muy grandes. Estas instrucciones ya dejan de existir en Visual Basic. Allí, en un entorno totalmente gráfico hay una instrucción que nos permitirá hacer eso con mucha mayor flexibilidad e incluso leyendo la información directamente desde archivos de imágenes normales. En QBasic estas instrucciones tienen utilidad en casos muy sencillos de repetir patrones parecidos al que hemos visto en el ejemplo. c < cc3c'c$'c&c("c8' c"#%($$2"c"%c Para terminar con los gráficos vamos a ver una cosa muy sencilla, pero extremadamente útil. Se trata de una función que nos va a devolver el color que tiene un punto de la pantalla gráfica. Esto puede parecer algo que no sirve para mucho, pero como ahora veremos nos va a ser de mucha utilidad para resolver una gran cantidad de problemas relacionados con los gráficos de una forma bastante sencilla, aunque poco eficiente. Vamos a lo que vamos, y vamos a ver la sintaxis de esta función: POINT (h,v) Así de sencillo. Usamos la función dentro de una expresión o a la derecha del operador de asignación y le pasamos como parámetros
c c
las coordenadas del punto y nos devuelve el valor de color correspondiente. Una cosa importante es que no podemos usar la palabra clave STEP para indicar coordenadas relativas, por lo tanto algo como... punto = POINT STEP (10, 10) ...estaría mal y daría un error detectado incluso por el editor al intentar dar un salto de línea. Si especificamos una coordenada que esté fuera de la pantalla, no pasa nada, simplemente se devuelve el valor -1. La instrucción POINT nos devuelve el valor del atributo de color del punto que especifiquemos, independientemente de que el color que se representa en la pantalla haya sido alterado por alguna instrucción PALETTE. Esto nos permite por ejemplo convertir en negro varios colores para que coincidan con el fondo, dibujar figuras con estos colores "invisibles" y después detectar su presencia con la instrucción POINT en alguno de los métodos que se describen a continuación, especialmente el de mapas de durezas utilizado ampliamente para la programación de videojuegos. Esta función es bastante útil en la pantalla de gráficos. Una aplicación interesante puede ser la implementación de una especie de "pantógrafo virtual" con el que iremos copiando punto por punto un área de la pantalla a otra posición con la posibilidad de cambiar el tamaño o los colores de la muestras obtenidas. Esto se hace metiendo dentro de dos bucles FOR anidados la instrucción POINT y una instrucción PSET para dibujar los puntos del mismo color en otra parte de la pantalla. Con este ejemplo vamos a copiar un trozo de la pantalla a otra posición 200 pixeles más a la derecha y con el doble de tamaño. FOR h = 0 TO 49 FOR v = 0 TO 49 c=POINT(h, v) PSET(h * 2) + 200, v * 2), c NEXT NEXT Este método es muy lento, pero para áreas pequeñas nos da grandes posibilidades cambiando todos o alguno de los los colores, no dibujando alguno o haciendo otras operaciones matemáticas. También en este ejemplo aparecerían puntos separados en la nueva ubicación. Podemos arreglar esto usando instrucciones LINE, circunferencias, rectas que vengan de un punto, botones dibujados con varias LINE, etc. Los resultados pueden ser bastante vistosos. Estos son algunos ejemplos de los resultados que se pueden obtener usando esta técnica.
c c
Otra aplicación ampliamente utilizada de la instrucción POINT en los videojuegos es lo que se conoce como detección anticipada de color o mapas de durezas. Esto sirve para que por ejemplo en un juego de laberintos no podamos meter a nuestro personaje a través de las paredes. Si no lo hiciéramos así tendríamos que desarrollar una sofisticada estructura de datos con matrices o algún otro algoritmo que puede que en proyectos pequeños no merezca la pena. De esta forma sólo hay que saber que si el fondo por el que se va a mover nuestro personaje es siempre negro, por ejemplo, a todas las partes de la pantalla que sean de otro color no va a poder entrar. Esto se evita comprobando el color de la nueva posición antes de desplazarnos a ella. Los mapas de durezas son una representación de la pantalla a menor resolución que el original y de un color que ha sido convertido por la instrucción PALETTE en invisible al coincidir con el fondo. Se usan normalmente en videojuegos sencillos de plataformas para saber exactamente por que parte de los escenarios se puede mover el personaje, donde están las plataformas, etc... trasladando las coordenadas reales de la pantalla a las del mapa de dureza para comprobar los colores sin interferir con los detalles del escenario original.
c c
Se pueden ver ejemplos de utilización de estas técnicas de programación y de algunas otras parecidas en la sección de videojuegos terminados de esta página web. Otra aplicación menos útil de la función POINT es que nos devuelva la posición actual del cursor de gráficos. Como una función sólo nos puede devolver un valor, le tenemos que decir lo que queremos que nos devuelva, con estos números. POINT(0) Devuelve el valor X (horizontal) de la coordenada actual en la pantalla o marco de visualización activo (Medida en píxeles). POINT(1) Devuelve el valor Y (vertical) de la coordenada actual en la pantalla o marco de visualización activo (Medida en píxeles). POINT(2) Devuelve el valor X (horizontal) de la coordenada lógica actual de nuestro sistema de coordenadas personalizado si lo tenemos definido. POINT(3) Devuelve el valor Y (vertical) de la coordenada lógica actual de nuestro sistema de coordenadas personalizado si lo tenemos definido. Si no tenemos definido un sistema de coordenadas personalizado (lo más normal), POINT(0) devolverá lo mismo que POINT(2) y POINT(1) lo mismo que POINT(3) ya que siempre estamos contando en pixeles.
c c
c < /cc#$ c%8%c"c&c +4$c En la pantalla gráfica también tenemos la posibilidad de escribir textos usando la instrucción PRINT de la misma forma que hacíamos en la pantalla normal en modo texto, pero nos vamos a encontrar con una serie de inconvenientes que seguramente nos van a hacer de desistir en la mayoría de los casos. Vamos a ver cuales son estos inconvenientes y la forma de evitarlos en la medida de lo posible. Las pantalla de texto a la que estamos acostumbrada es extremadamente rápida, pero en la pantalla de gráficos todo es más lento. Puede ocurrir incluso que veamos aparecer nuestro texto línea por línea como en las películas si nuestro ordenador tiene en este momento una carga de trabajo más alta de lo normal por culpa de otros programas que esté ejecutando Windows en ese momento. En las instrucciones de dibujo ya nos hemos acostumbrado a dibujar nuestras figuras exactamente en la posición de pixel que queramos, pero con la instrucción PRINT nos tenemos que acomodar a la línea y columna más próxima al sitio que queramos usando instrucciones LOCATE y puede que esto no siempre nos venga bien para encajar las letras en el sitio justo. Tendremos que adecuar nuestros gráficos al texto y no al contrario. En la pantalla en modo SCREEN 12 tenemos 80 columnas y 30 líneas para escribir. Los caracteres se dibujan con un tamaño algo más pequeño al del modo texto y con una definición aceptable. Een modo SCREEN 13 tenemos 40 columnas y 25 líneas. Los textos escritos aquí tendrán un aspecto parecido al del teletexto, con unos caracteres de doble ancho. Como este modo de pantalla es de baja resolución los caracteres aparecerán muy pixelados. Si llegamos a escribir en las líneas más bajas de la pantalla sin usar un punto y coma al final de las instrucciones PRINT, se producirá el desplazamiento automático arrastrando hacia arriba el texto y también nuestros gráficos. Como color para las letras podemos usar cualquiera de los que nos permite nuestro modo de pantalla (16 o 256), pero el fondo va a ser siempre y obligatoriamente del color 0 (negro). No se permite el uso de colores intermitentes. A diferencia de lo que pudiéramos pensar, las letras no se dibujan transparentes sobre lo que ya haya dibujado en la pantalla, sino que siempre van a ir sobre un recuadro negro. Este negro lo podemos cambiar redefiniendo el color 0 con una instrucción PALETTE, pero de todas formas esto es un grave inconveniente si queremos escribir sobre un fondo ya dibujado. Estos inconvenientes hacen que normalmente los rótulos que aparecen en nuestras pantallas en modo gráfico no se escriban utilizando instrucciones PRINT. Los lenguajes de programación más avanzados incluyen bibliotecas de fuentes y las correspondientes instrucciones de dibujo para escribir utilizándolas, así como funciones que nos permiten averiguar el ancho de los textos y otras cosas. Por
"c c
norma general estas tipografías no incluyen las letras acentuadas ni la eñe ni el símbolo del Euro. En QBasic vamos a tener que ser nosotros mismos los que nos construyamos nuestras instrucciones personalizadas para escribir en modo gráfico. En todo caso nos tendremos que molestar en dibujar todos y cada uno de los símbolos que vamos a querer escribir (Letras mayúsculas y minúsculas, números, signos de puntuación y matemáticos, caracteres especiales, etc...) Hay dos posibilidades: *c $ cc cc Consiste en ir dibujando cada carácter punto por punto usando instrucciones PSET. Esto nos da la posibilidad de dibujar los caracteres usando varios colores para formar por ejemplo un degradado. El inconveniente es que los caracteres podrán cambiar a tamaño solamente si el nuevo es un múltiplo del anterior. Esto se consigue en vez de dibujar puntos, dibujando cuadraditos de dos por dos píxeles o tres por tres... A medida que aumentemos de tamaño los caracteres perderán resolución. También hay que tener en cuenta que dibujar las letras punto por punto puede ser un proceso bastante lento. *c $ c. c Cada carácter es dibujado por una instrucción DRAW, lo que permite cambiar fácilmente su tamaño usando el parámetro "s" dentro de la orden DRAW. Los caracteres así dibujado siempre mantendrán su resolución aunque normalmente presentarán un aspecto de líneas muy finas porque no es recomendable intentar rellenar áreas si las figuras se van a escalar. La definición de los puntos o trazos que componen cada carácter quedará almacenada en un vector para ser leída cada vez que sea necesario. Para llenar el vector se harán asignaciones directas de literales o bien se obtendrá la información de un fichero secuencial que leeremos al principio del programa. En la sección de esta web dedicada a programas gráficos y videojuegos se pueden ver ejemplos de utilización de tipografías de ambos tipos.
| c ccc c c
Todo programa de gestión que se precie debe ser capaz de generar listados o informes y poderlos sacar por la impresora. Esta operación en QBasic es sencilla. Basta con enviar lo que queremos imprimir línea por línea a la impresora con la instrucción '"% de la misma forma que hemos venido usando la instrucción PRINT. Vamos con un ejemplo. LPRINT "PRUEBA DE IMPRESIÓN DESDE QBASIC" LPRINT "====================================" LPRINT "Hoy es "; DATE$; " y son las "; TIME$ LPRINT "La raíz de 256 es"; SQR(256) El resultado sería que obtendríamos por impresora un papel con algo parecido a lo siguiente:
#c c
c cc c c c c dcccccdccc c !"c#ccc Si queremos imprimir en posiciones específicas, como por ejemplo para rellenar los casilleros de un recibo, tendremos que ajustar las posiciones imprimiendo líneas en blanco (Usando LPRINT sin argumentos) y metiendo espacios entre las palabras. Lo importante es recordar que siempre se empieza en la parte superior izquierda del papel y se avanza línea por línea hacia abajo. No podemos saltar hacia atrás ya que el papel nunca corre para atrás dentro de la impresora. Hasta aquí todo muy bonito, pero ahora vienen los problemas. El primer problema es que en este proceso se pueden dar una gran variedad de errores que hagan que nuestro programa quede detenido, por lo que habrá que hacer un control de errores similar al que hacemos en los procesos de manejo de ficheros. Los errores que pueden ocurrir pueden ir desde que ni siquiera el usuario tenga impresora, que esta esté apagada o desconectada del ordenador, que no tenga papel o que haya algún fallo durante la transmisión de los datos. Estos errores vienen identificados cada uno con su código y se manejan con una estructura similar a la que vimos para el caso de los ficheros. Algunos de los códigos de error más comunes relacionados con la impresión son los siguientes: $2& c&cc
&#$$2"c
c
'Scccc.c
/c
4 cc.c
/
cc.cc K c
=@c
&.ccc
=Cc
& cc?Jcc c
<;c
c c
El trabajo con las impresoras es extremadamente complicado debido a que existen gran cantidad de controladores de impresión distintos, por lo que puede que aunque nuestro programa funcione bien y no se produzca ningún error, el trabajo no se llegue a imprimir por culpa de conflictos con el controlador o con el sistema operativo. QBasic en particular y los programas para MS-DOS en general tienen muchas limitaciones para imprimir. En nuestro caso solo
$c c
imprimiremos a través de la impresora que esté conectada al primer puerto paralelo de nuestro ordenador (LPT1), por lo tanto si tenemos una impresora USB o bien una conectada a otro equipo de la red local, en principio ya no podemos imprimir desde QBasic. Se podría conseguir instalando una serie de controladores y programas especiales que hagan operaciones como redireccionar los puertos, cosa que puede que no merezca la pena si solo usamos QBasic para aprender a programar. Otro problema es la tipografía y el tamaño de letra a utilizar. En todo caso se usará la que tenga por defecto la impresora que será normalmente algún tipo Courier de ancho fijo y con un tamaño como para que quepan 80 caracteres por línea. En las impresoras antiguas se puede cambiar este tipo de letra tocando determinados botones o moviendo algunos conmutadores internos. Nuestro programa podría llegar a cambiar esto, así como usar negritas, cursivas y pocos efectos mas insertando determinados códigos de caracteres especiales entre las palabras de nuestras instrucciones LPRINT, pero esto sería específico para cada modelo de impresora. Estos códigos de control o secuencias de escape vendrán detallados en el manual de la impresora (o no), pero no conviene usarlos a no ser que estemos haciendo un programa específico para ella, ya que con toda seguridad no van a funcionar en otra impresora de otro tipo. Si la impresión de textos es complicada, la impresión de gráficos ya es poco menos que imposible. Siguiendo detalladamente las instrucciones que vengan en el manual de la impresora, si es que vienen, el proceso consistiría básicamente en tratar a la impresora como si fuera un fichero de acceso directo, abriéndola con una orden de tipo... OPEN LPT1 FOR OUTPUT AS #1 Y escribiendo en dicho fichero los códigos de control adecuados junto con la información de los pixels que forman la imagen en formato binario, vamos, un lío muy grande. Y encima todo en blanco y negro. Visto esto se puede comprobar que imprimir desde QBasic es bastante entretenido, y todo para conseguir unos resultados muy pobres y poco innovadores a la hora de aprender a programar (Es igual que usar instrucciones PRINT). También hay que tener en cuenta que cada prueba que hagamos conlleva el gasto de una hoja de papel y de tinta, mientras que en la pantalla podemos escribir todas las veces que queramos de forma totalmente gratuita. Si a esto añadimos que al usar QBasic en Windows con las actuales impresoras USB se ha perdido completamente la compatibilidad y que en Visual Basic y otros lenguajes modernos el proceso de impresión es bastante distinto comprendemos fácilmente que no nos merece la pena preocuparnos demasiado de aprender a imprimir desde los programas de QBasic.
"c c
| c cc |c ccc$c En la época que apareció QBasic no era muy común que los ordenadores tuvieran un sistema de sonido como los que hoy conocemos, pero sí podemos emitir algunos sonidos a través del altavoz interno que tienen todos los PCs. La instrucción más sencilla que tenemos para emitir sonidos es esta... BEEP omo su propio nombre nos sugiere, lo que hace es hacer sonar por el altavoz interno un pitido similar al que se oye cuando encendemos el ordenador. Si estamos ejecutando QBasic desde Windows y tenemos una tarjeta de sonido, entonces la instrucción BEEP lo que hace es generar el sonido predeterminado de Windows a través de los altavoces multimedia de nuestro equipo. Es importante entender que durante el tiempo que dura el sonido, sea el pitido o el wav de Windows, la ejecución del programa queda detenida. Otra instrucción un poco más avanzada para reproducir sonidos es la siguiente... SOUND frecuencia, duración La frecuencia la tenemos que expresar en hertzios. Se admiten valores entre 37 y 32767. Un valor bajo indica un sonido grave, y uno alto un sonido agudo. Valores aceptables están entre 300 y 3000 hertzios. Por debajo se oyen mal, y por encima pueden resultar molestos. La duración viene indicada en 1/18 segundos. Esto quiere decir que si ponemos como duración 18 el sonido durará un segundo, si ponemos 36 durará 2 segundos, si ponemos 9 durará medio segundo y así con todos los valores. Es importante no poner valores muy altos (No mas de dos o tres segundos) ya que el sonido no parará hasta que no acabe el tiempo indicado, incluso aunque el programa termine o lo detengamos pulsando CONTROL+PAUSA. La instrucción SOUND nos va a servir para dar pitidos. En algunos casos nos puede interesar dar pitidos de mas de un tono con varias instrucciones SOUND o incluso usar bucles para conseguir cosas como esta... RJMGB - Hecho en Ronda - 1996 FOR v = 1 TO 5 FOR h = 1 TO 5 SOUND (h * 100) + (v * 200) + 100, 2 SOUND (v * 200) + 600, 2 SOUND (h * 200) + 400, 2 NEXT NEXT Si nos atrevemos con la música, por ejemplo para los videojuegos, QBasic nos provee de un mecanismo para componer melodías, parecido al de los timbres de los teléfonos móviles...
" c c
PLAY "cadenacomandos" Su funcionamiento es similar al de la orden DRAW que vimos en el tema de gráficos. Se le pasa una cadena de comandos entre comillas formada por una serie de letras y números que indican a QBasic la música que tiene que tocar. En la ayuda de QBasic se detallan los comandos que hay disponibles para esta instrucción. Si tienes unos conocimientos mínimos de música serás capaz de componer algún tipo de sintonías para que suenen en tus programas. Estos son instrucciones PLAY (Cada una ocupa una línea entera seguida, aunque aquí pueda aparecer cortada). PLAY "MBT180o2P2P8L8GGGL2E-P24P8L8FFFL2D" PLAY "MBo3L8ED+ED+Eo2Bo3DCL2o2A" PLAY "MNT250L8O2DL4F+L8F+F+EF+L4G.F+L8F+L4EL8EEDEL4F+. DL8DL4F+L8F+F+EF+L4G. BL8BL4AL8AL4GL8EL2Dmn" En nuestros programas no conviene abusar de la música y de los sonidos. Hay que tener en cuenta que en los sistemas de sonido modernos el usuario tiene la posibilidad de ajustar el volumen de los altavoces o incluso de apagarlos, pero al usar el altavoz interno del PC el usuario no tiene más remedio que escuchar todos los sonidos que produzca nuestro programa. Normalmente se usan pitidos cortos para reaccionar ante ciertas acciones. Si nuestro programa hace un uso mas intensivo de pitidos y música sería conveniente dotarlo de un mecanismo de forma que el usuario pueda desactivarlos, por ejemplo usando variables globales y condiciones antes de intentar hacer algún sonido... INPUT "Quieres oír los sonidos? - (S/N):"; sonido$ R R...resto del programa... R IF sonido$ = "S" THEN SOUND 100,5 En todo caso el uso de los sonidos tendría que ser solo en momentos puntuales. Al pasar a otros entornos de programación más avanzados ya tendremos la posibilidad de aprovechar todas las posibilidades de la tarjeta de sonido.
| cc cc c c c c c%c %cc c " c
#%" c
#(45c
&#$$2"c
9c
$ cc c . c E%:cc c Gc
6"c7c +8c
;c c<=
"c c
"% c
'" c
#" 'c
&( 'c
>c
c E"?c 0cc Gc
<=@c c <=
Ac
c c E"?c cc Gc
x< @ =@c
c x< @ =
1c
& cc -c c E"?cc Gc
0@:x;/c c 0@:x;/c
Bc
& cc c -c E"?cc c Gc
0C:x;c c 0C:x;c
c L !&c
" c
Mcc
# c
cc
c
Ncc
-c
Kcc
&.-c
&cc
cc .-c E-Gc
Occ
c
c- !&c
" c
!"&c
c7c
c
cc
"%c
c cc -c
"c c 8c
cc:.c
3c
cc. c
c
cc -c cc - !&c
" c
Pcc
c
Qcc
Icc
Rcc
cc
QPcc
Icc cc
RPcc
cc cc
RQcc
&cc
cc c &c
#"%!$2"c
%8%c
#'($2"c
4("&&!&c &c$'c
#$"c ;c
J Fcc :c
@;c:c/c c
c
x=cc
#$"c xc
J Fc Jc
@;c:c;c c
=;c:c@;c :c
x=cc
#$"c xc
J Fc Jc
;c:c/c c
;c:c;;c :c
/=cc
cx=ccc c#$"c;cIc#$"cxcE3 !G
$cc c cc#$"c;
"c c
cc/=cc c#$"cx
| c cc c | cc cc c
"c c
cc c:
| c ccc c%cc c cc *c *c *c *c *c *c
xcc-c cc cIc c&# : cc -cc c&# : cc -ccc Jc&4 cc$J -ccJcc /cc(cc ccc =cc!. cc
c xcc-c cc cIc c&# :c Un emulador es un programa informático que nos permite usar aplicaciones de un determinado sistema en otro distinto. Por ejemplo abrir juegos de una videoconsola en un ordenador, usar programas y documentos de Macintosh en Windows, etc... La única limitación es que el sistema emulado debe ser de menores prestaciones que el sistema que tenemos físicamente. Esto de los emuladores se usa principalmente en el mundo de los videojuegos.
"c c
QBasic se usaba en los años 1990 en ordenadores Intel x86 con apenas 1MB de memoria RAM, tarjeta gráfica y monitor en blanco y negro o con color VGA, sin tarjeta de sonido y bajo el sistema operativo MS-DOS con todas sus limitaciones. Ahora usamos ordenadores con cientos de megas de memoria RAM, tarjeta de sonido, sistema gráfico muy avanzado optimizado para 3D y sistemas operativos muy avanzados como Windows XP o Linux. Es posible que QBasic no funcione de la misma forma que lo hacía en los ordenadores para los que fue diseñado. Normalmente el editor de código y los programas en modo texto no suelen dar grandes problemas, pero cuando entramos en los modos de pantalla gráfica ya empiezan los problemas con la pantalla y en programas complejos nos podemos encontrar que funcionan mucho más rápido o mucho más lento de como deberían. Si QBasic no funciona bien en los ordenadores modernos no creo que sea ningún grave problema para nadie. Pero en aquella época había una gran cantidad de videojuegos hechos para MS-DOS que por motivos comerciales eran de gran calidad. Estos videojuegos ahora no funcionan en los ordenadores actuales y no estamos dispuestos a dejarlos perder. Para eso ha aparecido DOSBox, un programa libre que emula a un ordenador antiguo con sistema operativo MS-DOS en un ordenador moderno. DOSBox está pensado para hacer funcionar videojuegos, pero también puede perfectamente con programas de otro tipo como QBasic. La principal ventaja que vamos a encontrar usando QBasic dentro de DOSBox va a ser en los programas gráficos. Los podremos ver cómodamente en una ventana de Windows sin cambiar de resolución, por lo que podremos abrirlos y cerrarlos todas las veces que queramos sin miedo a tener problemas con el monitor.
""c c
cc -cc c&# :c DOSBox es un programa libre que se distribuye bajo la licencia GNU. Es completamente gratuito y para conseguirlo lo único que hay que hacer es ir a la página web del proyecto y descargar la última versión disponible para nuestro sistema operativo. Existen versiones para Windows, para las distribuciones de Linux más conocidas y para otros sistemas operativos como Mac, OS/2 o BeOS. UKK&# : J En el caso de Windows, el archivo a descargar ocupa poco más de un megabyte. Una vez descargado lo abrimos y se iniciará el proceso automático de instalación. Aunque viene en inglés es muy sencillo, similar al de otros programas. Se pedirá el directorio de instalación, normalmente asegúrate de que se cree dentro de $ %&'(dc#c ) d* +$ para mayor comodidad.
Una vez instalado, aparecerá en el escritorio o en el menú de inicio un acceso directo para abrir el emulador. Si entras verás que DOSBox se abre en una ventana en modo texto parecida a la del símbolo del sistema de MS-DOS y que por defecto muestra la unidad ,$-
"#c c
El uso del emulador sería básicamente parecido al del sistema operativo MS-DOS, escribir órdenes como CD, MD, DIR, etc... Pero además hay que hacer otras cosas como à las unidades de disco igual que si estuviéramos en Linux y configurar determinados parámetros de cada juego o programa modificando un archivo de texto. Este proceso ya no es tan sencillo y por lo tanto lo haremos de otra forma, usando un o interface gráfico para Windows que hará estas tareas mucho más sencillas. Puedes cerrar la ventana de DOSBox escribiendo .'/ y pulsando la tecla Enter. A continuación instalaremos el entorno gráfico y nunca tendremos que usar el emulador directamente. cc -ccc Jc&4c c "4c & c cc;;
Para facilitar el uso del emulador DOSBox han aparecido muchos . Puedes instalar uno o varios simultáneamente sin problemas. En este caso usaremos D-Fend que es de los más conocidos. D-Fend también es software libre y se puede descargar gratuitamente desde su página de internet. UKK K
"$c c
El proceso de instalación es el normal de cualquier programa. Cuando te pida la carpeta de instalación asegúrate por comodidad que se crea dentro de $ %&'(dc#c) d* +$c
Una vez instalado, la primera vez que abras D-Fend aparecerá un cuadro de diálogo preguntando dónde está instalado DOSBox. Selecciona la carpeta donde lo instalaste anteriormente. c cc$J -ccJcc c El funcionamiento de D-Fend es muy simple. Lo que haremos es crear un perfil para cada juego o programa que queramos usar. Este perfil contiene información sobre la localización del programa ejecutable, el tipo de tarjeta de sonido, de monitor, cantidad de memoria que necesita, etc... Todos los perfiles se van acumulando en un índice en la pantalla principal de D-Fend y de esta forma cada vez que queramos abrir un juego será suficiente con seleccionarlo en este índice y nada más.
#c c
Lo que tenemos que hacer ahora mismo es crear un perfil para poder abrir QBasic. Existe un asistente paso a paso para ir introduciendo la información necesaria. Pulsa el botón que tiene un sombrero de mago, situado el primero a la izquierda de la barra de herramientas de D-Fend. El asistente consiste en una sucesión de cuadros de diálogo que van pidiendo información, cada vez que rellenes lo que pide pulsa el botón >> y pasamos al siguiente paso del asistente. *c cJcJ Aquí indicamos la localización de QBasic dc Jc U Aquí puedes escribir el nombre que quieras. Será lo que aparecerá en la lista de programas. Por ejemplo '% dc $c&# :c Jc c: Marca esta casilla para que se cierre el emulador al terminar y que no se quede la ventana abierta en negro. dc
c8 Pulsa el botón Browse y selecciona el archivo QBasic.exe en la carpeta donde tengas el QBasic. dc #c8 Deja este cuadro en blanco porque QBasic no tiene programa de instalación. *c cc cJcIc ccc. Los valores de esta pantalla afectan a la velocidad de funcionamiento del emulador y cual es la tarjeta gráfica. dc En esta pantalla puedes dejar los valores por defecto. Si algo no va bien ya lo cambiaremos más adelante.
# c c
$J cc Aquí tenemos que montar una unidad de disco virtual para que funcione el emulador. Crearemos una unidad C:\ ficticia a partir de una carpeta de nuestro disco duro, donde esté instalado QBasic. dc $ c Pulsa este botón para que se borren los puntos de montaje que haya podido crear automáticamente el asistente. A continuación crearemos uno nuevo para QBasic. dc ! Pulsa este botón para añadir un punto de montaje. Se abrirá un nuevo cuadro de diálogo para crear la unidad virtual. c cI Drive c #c.U Pulsa el botón browse y selecciona la carpeta donde tienes el QBasic. Deberás tener tus archivos .BAS dentro de esta carpeta para poderlos abrir porque todo lo que haya fuera de la carpeta que especifiques aquí no será visto por el emulador. Si quieres selecciona otra carpeta más arriba para abarcar más. Las normas de seguridad recomiendan no asignar la unidad c:\ auténtica entera, aunque si quieres puedes hacerlo sin problemas y tendrás acceso a todo el disco duro desde dentro del emulador. c &.c Etiqueta del volumen. Escribe aquí lo que quieras o déjalo en blanco. c cKc Puedes marcar esta casilla c c.c Normalmente la letra C c Pulsa OK para cerrar este cuadro de diálogo y seguir con el asistente *c $J c# Aquí configuramos la tarjeta de sonido. Como QBasic no la utiliza da igual lo que hagamos. dc En esta pantalla puedes dejar los valores por defecto. *c $J c (# Aquí configuramos más propiedades del sonido. Como QBasic no la utiliza da igual lo que hagamos. dc En esta pantalla puedes dejar los valores por defecto. *c $J c Aquí configuramos la música MIDI. Como QBasic no la utiliza da igual lo que hagamos. dc En esta pantalla puedes dejar los valores por defecto. *c $J c$c# ] Aquí configuramos el sonido a través del altavoz interno del ordenador que sí utiliza QBasic. dc c$c# ] Asegúrate de que esta casilla está marcada dc $c# ]c Deja el valor que tiene, 22.050 dc % Ic# ]c Deja el valor que tiene, 22.050 dc cIcc Puedes desmarcarla *c I Cantidad de memoria del ordenador emulado dc IcE G Puedes bajar este valor a 4 MB o menos. Ningún ordenador de la época tenía 16 megas de RAM dc 8#cIc# Puedes desmarcar estas casillas, QBasic solo usa memoria convencional. *c
#c c
Pulsa el botón ·''& y se habrá creado el nuevo perfil para QBasic en el índice. El asistente no ha pedido nada relacionado con el ratón porque la mayoría de los juegos no lo utilizan. Vamos a activarlo para que funcione en QBasic. En el índice de programas de D-Fend selecciona el perfil que acabamos de crear para QBasic y haz clic con el botón derecho del ratón. En el menú contextual selecciona cJ. Aparece un cuadro de diálogo con todas las propiedades del perfil. Están en distinto orden a como fueron saliendo en el asistente y hay muchas nuevas. En las pestañas de la parte superior selecciona , y en los controles que aparecen marca la casilla !c]c .
Hecho esto ya debería de funcionar todo perfectamente. Si algo falla o quieres afinar el rendimiento del emulador, entra en este cuadro de diálogo y cambia los valores de las distintas propiedades del perfil. c /cc(cc ccc c El funcionamiento de QBasic dentro del emulador es completamente igual a como funcionaría en el sistema operativo real. Encontraremos que al iniciar un programa en modo gráfico la ventana cambia de tamaño pero sigue siendo una ventana en vez de pasar a pantalla completa, motivo principal para estar usando el emulador. También el sonido seguramente se oirá a través de los altavoces del sistema de sonido del ordenador en vez de por el altavoz integrado en la torre. El ratón al principio nos puede despistar. El cursor será un carácter en modo texto en vez de la flecha típica de Windows. No se activará hasta que hagamos clic dentro de la ventana de QBasic. A partir de ahora desaparecerá la flecha y moveremos el cursor de QBasic. Al haber seleccionado Auto Lock Mouse, el cursor no puede salir de la
#c c
ventana, por lo que no alcanzaremos a tocar cosas como el menú de inicio o las otras ventanas aunque las estemos viendo alrededor en el escritorio. cT Tcccc c c cX\ccc c ccc cc?cc0ccc c -cc% c!M% c c c c c - Para volver a usar el ratón en QBasic haz clic dentro de la ventana. Hacer esto muchas veces puede causar que en QBasic deje de responder el teclado como viene ocurriendo en el sistema operativo real, siempre podrás guardar tu trabajo y salir activando los menús con el ratón. Otro inconveniente del emulador para los juegos donde hay que escribir y por lo tanto para QBasic es que no se utiliza un mapa de caracteres internacional, por lo que algunas letras acentuadas y algunos símbolos pueden variar de la representación de las teclas del teclado. Por este motivo es más cómodo hacer las tareas de escritura de código más largas usando QBasic sin el emulador, y dejar el emulador solo para abrir programas gráficos que ya están terminados o no necesitan escribir mucho código. Para escribir caracteres o símbolos que "no salen" al pulsar su tecla siempre puedes usar la cc- c!#$ o intentar alguna de estas teclas. #6 'c
%$'!c
^c Pc
cc
#6 'c
%$'!c
Rc
Rc
_c
`c
Oc
Ac
cc Ac
Kc
Ec
Gc
Gc
Pc
ac
,c
c =cc!. cc c Una vez que tenemos instalado el emulador DOSBox y el · podemos aprovechar para recuperar y usar con seguridad una gran cantidad de juegos antiguos de alta calidad y programas muy conocidos de MS-DOS que aunque ahora ya no sean útiles los podemos ver por curiosidad. Se pueden llegar a hacer cosas como instalar Windows 3.1 para usarlo desde dentro del emulador. Al mismo tiempo D-Fend nos permite mantener una lista muy organizada de todos los juegos que tengamos y de hacer cosas como capturas de pantallas y sonidos o crear accesos directos en el escritorio.
#c c
La mayoría de juegos antiguos han sido cedidos gratis por sus desarrolladores al dominio público o bien fueron programados por empresas que ya no existen y también pueden ser usados libremente. Infórmate de las condiciones de cada juego o programa que quieras usar. Hay páginas que se dedican a recopilar este tipo de juegos, conocidos como abandonware. Puedes encontrar muchos juegos pero siempre hay que tener en cuenta que su descarga tiene que ser totalmente gratuita y sin obligación de enviar mensajes SMS ni de descargar o instalar otro tipo de software ni nada.