Operaciones básicas de Entada y Salida

Por
Dispositivos de Entrada y Salida de un computador

Todos los programas para computador, requieren que el usuario ingrese los datos que son necesarios para llevar a cabo sus procesos, y estos programas a su vez, también deben generar la información que el usuario le solicite. Un computador cuenta con varios dispositivos que le permiten introducir los datos a sus programas; ya sea a través del teclado, del mouse, una unidad de almacenamiento, u otro dispositivo de entrada. Así también, cuenta con varios dispositivos para emitir información al usuario, como la pantalla, la impresora, u otro dispositivo de salida.

En el lenguaje Pascal, se ha implementado un conjunto de variables, registros, funciones, y procedimientos que nos permiten efectuar todas las operaciones de entrada y salida (E/S) de datos; todo ese conjunto se encuentra distribuido entre las unidades estándar System y Crt. En este artículo, sólo nos enfocaremos en las operaciones básicas de entrada y salida efectuadas a través del teclado y la pantalla.

Los registros Input y Output

Un registro es cualquier variable cuya definición está basada en estructuras compuestas por una o varias componentes de datos. Una de las clases de registros más utilizados, son los registros de tipo textual (Text), los cuales están constituidos por secuencias de caracteres distribuidas en forma de líneas; donde cada línea se separa una de otra mediante un delimitador de fin de línea: compuesto, en algunos sistemas, por un carácter de retorno de carro (ASCII #13) seguido por un carácter de salto de línea (ASCII #10). Los registros textuales normalmente se acceden en forma secuencial; es decir, que las operaciones de lectura o escritura se realizan desplazándose de un carácter a otro en orden consecutivo.

Los compiladores del lenguaje Pascal tienen definidos dos registros estándar de tipo textual: Input y Output. El registro Input es de solo lectura, y está asociado al registro estándar de entrada; el cual almacena los datos ingresados desde el teclado. Mientras que, el registro Output es de solo escritura, y está asociado al registro estándar de salida; el cual almacena los datos que se visualizan en la pantalla. Los registros estándar de entrada y salida son propios del sistema operativo (DOS).

Salida de datos por pantalla

Mediante el procedimiento Write podemos escribir uno o más valores sobre un registro de salida de tipo textual. La sintaxis para este procedimiento es la siguiente:

Write (R, P1 [, P2, …, Pn]);

Donde R es el registro de salida sobre el que se escriben los valores; y cada P es un parámetro de escritura, el cual representa una expresión de salida cuyo valor es lo que será escrito en el registro. Si R es especificado, la escritura se realiza sobre un registro asociado a un dispositivo o archivo externo; si es omitido, se asume por defecto que la escritura se efectúa en el registro estándar Output.

Todos los valores que se escriben en el registro Output, se visualizarán en la pantalla; lo cual nos permite emitir mensajes al usuario. El valor de cada expresión de salida puede ser del tipo Char, String, Real, Integer, o Boolean. Veamos un ejemplo:

Program E02_Salida_en_Pantalla;

{-----------------------------------------+
| Para ejecutar los programas de ejemplo, |
| en adelante, pulse las teclas CTRL+F9 y |
| luego ALT+F5 para ver sus resultados.   |
+-----------------------------------------}

Const
   Msg = 'Hello World!';

Begin

   Write (Msg);

End.

El procedimiento Write tiene otra variante: el procedimiento Writeln; ambos realizan la misma acción sobre un registro textual. La sintaxis es similar para ambos procedimientos, la diferencia está en que Write deja el cursor en la misma línea después efectuar la escritura; mientras que, Writeln produce un salto del cursor a la línea siguiente. A continuación tenemos un ejemplo de cómo podemos utilizarlo:

Program E03_Area_de_Figuras;

Var
   Area, Base, Altura: Integer;

Begin

   Writeln ('AREA DE UN RECTÁNGULO:');
   Altura := 8;
   Base := 10;
   Writeln ('Su base es .... ', Base, ' cm');
   Writeln ('su altura es .. ', Altura, ' cm');
   Writeln ('Y su área es .. ', Base * Altura, ' cm²');
   
   {ALT+F5 para ver todos los resultados}

End.

El Formato de Salida

Opcionalmente, en cada parámetro de escritura de los procedimientos Write y Writeln se puede especificar la anchura de campo y el número de posiciones decimales, mediante el siguiente formato:

Expresion[:AnchoCampo[:PosDecimal]]

Donde: Expresion es cualquier expresión de salida, AnchoCampo especifica el ancho (en caracteres) que tomará el valor de la expresión. PosDecimal indica el número de cifras decimales para la notación numérica en punto fijo; este atributo sólo se debe especificar cuando se trabaje con valores o expresiones que retornen valores de tipo Real, siempre y cuando AnchoCampo también haya sido especificado. Por ejemplo:

Program E04_Area_de_Figuras;

Const
   PI = 3.1416;

Var
   Area, Radio: Real;

Begin

   Writeln ('AREA DE UN CÍCULO:');
   Radio := 7;
   Area := PI * Sqr(Radio);
   Writeln ('Su radio es ... ', Radio:10:2, 'cm':5);
   Writeln ('Y su área es .. ', Area:10:2, 'cm²':5);
   
   {ALT+F5 para ver todos los resultados}

End.

Cuando la expresión de salida sea una cadena de caracteres (String), se tomará como mínima la anchura que se indique en AnchoCampo. La cadena no se truncará si su longitud es mayor que la anchura; pero si es menor, se le adicionarán espacios a la izquierda hasta completar la anchura especificada.

Igualmente ocurre cuando la expresión de salida sea un número real. Si la longitud de una cadena numérica es mayor que la indicada en AnchoCampo, las cifras enteras no se truncarán sino que se escribirán completas; si la longitud es menor, se le adicionarán espacios a la izquierda. Las cifras decimales si serán truncadas y redondeadas al número de posiciones indicado en PosDecimal.

Entrada de datos desde el teclado

La forma más habitual de introducir datos a un programa, es mediante el procedimiento Read; el cual lee uno o varios valores desde un registro de entrada textual y luego los almacena en una o más variables. Su sintaxis es la siguiente:

Read (R, V1 [, V2, …, Vn]);

Donde R, es el registro de entrada desde el cual se leen los valores; mientras que cada V es cualquier variable del tipo Char, String, Real, o Integer. Cuando R es especificado, la lectura se realiza desde un registro asociado a un archivo externo; pero si es omitido, se asume por defecto que la lectura es desde el registro de estándar Input.

Al efectuar la lectura desde el registro Input, el procedimiento Read espera a que el usuario ingrese una secuencia de caracteres desde el teclado (debe haber una correspondencia entre el orden y tipo de las variables, con los datos ingresados). Luego de que este registro sea leído, los valores hallados en su secuencia de caracteres serán interpretados y asignados a cada variable. Normalmente, la lectura se completa hasta haber asignado un valor a todas las variables; o hasta detectar la tecla enter. Como ejemplo, tomemos el siguiente programa:

Program E05_Area_de_Figuras;

Var
   Area, Base, Altura: Real;

Begin

   Writeln ('AREA DE UN TRIÁNGULO:');
   Write ('Digite la longitud de base (cm) . ');
   Read (Base);
   Write ('Digite la altura (cm) ........... ');
   Read (Altura);
   Area := (Base * Altura) / 2;
   Writeln ('El área del triángulo es ........ ', Area:0:2, 'cm²':5);
   
   {ALT+F5 para ver todos los resultados}

End.

Existe también otra variante para el procedimiento Read: el procedimiento Readln, que realiza lo mismo que Read; pero con la diferencia de que genera saltos del cursor para poder leer líneas sucesivas de un registro. Veamos un ejemplo muy interesante:

Program E06_Entrada_Salida;

Var 
   a, b, c, d: Char;

Begin

   Writeln ('REALICE LO SIGUIENTE:');
   Writeln ('1. Digite 4 caracteres diferente y pulse Enter');
   Writeln ('2. Luego otros 4 caracteres diferentes y pulse Enter');
   Writeln;

   Readln (a);
   Read (b);
   Read (c, d);

   Writeln;
   Writeln ('Observe la secuencia resultante: ', a, '-', b, '-', c, '-', d);
   Writeln ('¿Qué caracteres fueron omitidos?');

   {ALT+F5 para ver todos los resultados}

End.

Lo interesante del programa anterior es que nos permite observar las diferencias entre Read y Readln; ya que al ejecutarlo, se podrá notar que: solamente la llamada a Readln leerá la primera secuencia de caracteres, y almacenará el primer carácter leído en la variable a; los caracteres restantes serán omitidos al no tener más variables donde almacenarlos; posteriormente, las dos llamadas a Read leerán conjuntamente la segunda secuencia de caracteres, y almacenarán ordenadamente sus primeros tres caracteres en las variables b, c, y d.

Comportamiento de Read y Readln

Si usted procede a experimentar con los procedimientos de lectura, ejecutándolos con distintos tipos de variables, podrá observar lo siguiente:

  • Con una variable de tipo Char, se lee sólo un carácter del registro, y asigna ese carácter a la variable omitiendo el resto de caracteres. Este tipo de variables también pueden aceptar caracteres de control, como el carácter de retorno de carro (ASCII #13).
  • Con una variable de tipo Real o Integer, se explora una secuencia de caracteres hasta completar un número válido. Cada cadena numérica debe estar separada una de otras mediante un espaciador (espacio en blanco, tabulación, o delimitador de fin de línea). La lectura de un valor numérico se completa al encontrar el primer espaciador subsiguiente a la cadena numérica. Si la cadena numérica leída es acorde al formato de dato esperado, el valor es asignado a la variable; en caso contrario, se produce un error de E/S.
  • Con una variable de tipo String, se leen todos los caracteres hasta el siguiente delimitador de fin de línea, pero sin incluirlo. La cadena resultante es asignada a la variable. Si la cadena leída es más larga que la longitud máxima de la variable, la cadena resultante será truncada.
  • Después de asignar un valor a una variable, Read y Readln continúan el curso de lectura explorando la secuencia de caracteres que aún le falta por leer; hasta llegar al final del registro.
  • Si al llegar al final del registro se intenta asignar un valor a una variable, Read y Readln retornarán: un marcador de fin de registro (ASCII #26), si la variable es de tipo Char; un valor cero, si la variable es de tipo Real o Integer; o una cadena vacía, si la variable es de tipo String.

Limitaciones de Read y Readln

  • Con una variable de tipo String, el procedimiento Read nunca saltará a leer la línea siguiente de un registro después de efectuar la lectura. Por lo tanto, no se podrán ejecutar llamadas múltiples de Read para leer cadenas sucesivas; ya que después de la primera llamada de Read, cada llamada subsiguiente solo verá el final de la primera línea leída y retornará una cadena vacía. Para leer cadenas de caracteres en líneas sucesivas, en lugar de Read, ejecute llamadas múltiples de Readln.
  • El procedimiento Readln solamente trabaja sobre registros de tipo textual, siempre y cuando estén habilitados para operaciones de entrada.

Utilizando la Unidad Crt

La Unidad Crt nos ofrece una variedad de poderosas rutinas que permiten tener un completo control de las características del computador (en especial las de pantalla), tales como configuración del modo de pantalla, uso de ventanas, uso de códigos de caracteres extendido, manejo de colores, y funciones de sonido básico.

Para utilizar la unidad Crt, simplemente se debe incluir en la cláusula Uses de un programa; tal como se haría con cualquier otra unidad:

Uses Crt;

Normalmente, los programas emiten su salida en pantalla a través del sistema operativo, lo cual añade mucha sobrecarga. Al utilizar la unidad Crt, los registros Input y Output dejan de hacer referencia a los registros estándar de entrada y salida del sistema operativo, para pasar a ser asignados a la misma Unidad Crt.

De todas las principales rutinas incluidas en Crt, quizás las que más nos conviene estudiar primero son el procedimiento ClrScr, y las funciones ReadKey y KeyPressed. Con estas rutinas podremos aumentar un poco más nuestras posibilidades de programación; tal como veremos a continuación.

El procedimiento ClrScr

Este procedimiento limpia todo el contenido de la pantalla, o de la ventana activa, y luego coloca el cursor en la esquina superior izquierda. Su sintaxis es simplemente:

ClrScr;

El procedimiento ClrScr lo que realmente hace: es sobrescribir toda la pantalla con caracteres de espacio en blanco, con los atributos de texto actualmente definidos. Por ejemplo, si el color actual de resaltado de texto es azul, al ejecutarse este procedimiento la pantalla (o la ventana activa) tomará totalmente el color azul como fondo.

La función ReadKey

La función ReadKey permite leer un carácter desde el teclado. Al llamar a esta función, el programa detiene su ejecución y espera a que el usuario pulse una tecla; luego retorna el cáracter de la tecla pulsada. Esta función resulta muy útil para comprobar estados del teclado; o para controlar acciones del programa mediante la pulsación de teclas de caracteres simples.

A continuación, veamos un ejemplo donde ponemos en práctica al procedimiento ClrScr y a la función ReadKey:

Program E07_Lectura_de_Teclas;

Uses
   Crt;

Var
   Ch: Char;

Begin

   ClrScr; { Borra toda la pantalla: }
   
   Writeln ('Por favor, pulse cualquier tecla [a – z]');

   Ch := Readkey;  { La tela leída se almacena en Ch }

   Writeln ('La tecla pulsada fue: ', Ch);
   Write ('Ahora pulse otra tecla para salir');

   Readkey; { Espera hasta que se pulse una tecla}

End.

Teclas especiales como las tecla Alt, las teclas de funciones, las teclas del cursor, entre otras; generan un código de exploración (ScanCode) extendido. Cuando una tecla especial es presionada, ReadKey retorna un caracter nulo (ASCII #0) al invocarse por primera vez; y luego retorna el código de exploración al invocarse por segunda vez. Para comprobar lo dicho, ejecute el siguiente programa:

Program Scan_Codes;

Uses
   Crt;
Var
   Ch: char;

Begin

   Clrscr;
   Writeln ('Presione Left/Right, o Esc para salir');

   Repeat

      Ch := Readkey; { Primera llamada }

      Case Ch of

        #0: Begin
             Ch := Readkey; { Lee código de exploración }
             Case Ch of
               #75: WriteLn ('Left');
               #77: WriteLn ('Right');
             End;
          End;

        #27: WriteLn ('Esc');

      End

   Until Ch = #27 { Tecla Esc }

End.

Notas sobre la función ReadKey

  • El valor retornado por esta función es de tipo Char, y el caracter leído no se visualiza en la pantalla.
  • Sí la función KeyPressed retorna el valor True antes de una llamada a ReadKey, la lectura se cancela y no espera a que se pulse una tecla; en caso contrario, la lectura se realiza normalmente.

La Función KeyPressed

La función KeyPressed devuelve el valor True si el usuario ha pulsado alguna tecla, o devuelve el valor False si no ha pulsado ninguna tecla. Esta función no detecta las pulsaciones de teclas alternativas tales como Shit, Alt, NumLock, entre otras. Normalmente, la función KeyPressed se utiliza dentro de instrucciones repetitivas.

Para tener un ejemplo, veamos el siguiente programa; el cual se cierra cuando el usuario presione una tecla, o hasta que pasen aproximadamente 30 segundos:

Program E09_Test_de_Teclas;

Uses
   Crt;

Var
   Cont: Integer;

Begin

   Repeat 
      ClrScr;
      Cont := Cont + 1;
      Writeln ('ELIJA UNA OPCIÓN:');
      Writeln ('Pulse una tecla para salir ya, o espere 30s');
      Writeln ('para salir automáticamente [', Cont:2, 's]');
      Delay (1000)	  
   Until (Cont >= 30) Or KeyPressed;

End.

En el ejemplo anterior, todas las instrucciones anidadas dentro del bloque Repeat–Until se ejecutarán repetidas veces; hasta que la expresión Cont >= 30 devuelva el valor True, o hasta que KeyPresed devuelva el valor True.