639
Programación Lenguajes de programación Al desarrollarse las primeras computadoras electrónicas, se vio la necesidad de programarlas, es decir, de almacenar en memoria la información sobre la tarea que iban a ejecutar. Las primeras se usaban como calculadoras simples; se les indicaban los pasos de cálculo, uno por uno. John Von Neumann desarrolló el modelo que lleva su nombre, para describir este concepto de "programa almacenado". En este modelo, se tiene una abstracción de la memoria como un conjunto de celdas, que almacenan simplemente números. Estos números pueden representar dos cosas: los datos, sobre los que va a trabajar el programa; o bien, el programa en sí. ¿Cómo es que describimos un programa como números? Se tenía el problema de representar las acciones que iba a realizar la computadora, y que la memoria, al estar compuesta por switches correspondientes al concepto de bit, solamente nos permitía almacenar números binarios. La solución que se tomó fue la siguiente: a cada acción que sea capaz de realizar nuestra computadora, asociarle un número, que será su código de operación (opcode). Por ejemplo, una calculadora programable simple podría asignar los opcodes: 1 = SUMA, 2 = RESTA, 3 = MULTIPLICA, 4 = DIVIDE. Supongamos que queremos realizar la operación 5 * 3 + 2, en la calculadora descrita arriba. En memoria, podríamos "escribir" el programa de la siguiente forma: Localidad Opcode Significado Comentario 0 5 5 En esta localidad, tenemos el primer número de la fórmula 1 3 * En esta localidad, tenemos el opcode que representa la multiplicación. 2 3 3 En esta localidad, tenemos el segundo número de la fórmula 3 1 + En esta localidad, tenemos el opcode que representa la suma. 4 2 2 En esta localidad, tenemos el último número de la fórmula Podemos ver que con esta representación, es simple expresar las operaciones de las que es capaz el hardware (en este caso, nuestra calculadora imaginaria), en la memoria. La descripción y uso de los opcodes es lo que llamamos lenguaje de máquina . Es decir, la lista de códigos que la máquina va a interpretar como instrucciones, describe las capacidades de programación que tenemos de ella; es el lenguaje más primitivo, depende directamente del hardware, y requiere del programador que conozca el funcionamiento de la máquina al más bajo nivel. los lenguajes más primitivos fueron los lenguajes de máquina. Esto, ya que el hardware se desarrolló antes del software, y además cualquier software finalmente tiene que expresarse en el lenguaje que maneja el hardware. La programación en esos momentos era sumamente tediosa, pues el programador tenía que "bajarse" al nivel de la máquina y decirle, paso a pasito, cada punto de la tarea que tenía que realizar. Además, debía expresarlo en forma numérica; y por supuesto, este proceso era propenso a errores, con lo que la productividad del programador era muy limitada. Sin embargo, hay que recordar que en estos momentos, simplemente aún no existía alternativa. El primer gran avance que se dio, como ya se comentó, fue la abstracción dada por el Lenguaje Ensamblador, y con él, el nacimiento de las primeras herramientas automáticas para generar el 1

Programacion en Java

Embed Size (px)

Citation preview

Page 1: Programacion en Java

Programación

Lenguajes de programación

Al desarrollarse las primeras computadoras electrónicas, se vio la necesidad de programarlas, es decir, de almacenar en memoria la información sobre la tarea que iban a ejecutar. Las primeras se usaban como calculadoras simples; se les indicaban los pasos de cálculo, uno por uno.

John Von Neumann desarrolló el modelo que lleva su nombre, para describir este concepto de "programa almacenado". En este modelo, se tiene una abstracción de la memoria como un conjunto de celdas, que almacenan simplemente números. Estos números pueden representar dos cosas: los datos, sobre los que va a trabajar el programa; o bien, el programa en sí.

¿Cómo es que describimos un programa como números? Se tenía el problema de representar las acciones que iba a realizar la computadora, y que la memoria, al estar compuesta por switches correspondientes al concepto de bit, solamente nos permitía almacenar números binarios.

La solución que se tomó fue la siguiente: a cada acción que sea capaz de realizar nuestra computadora, asociarle un número, que será su código de operación (opcode). Por ejemplo, una calculadora programable simple podría asignar los opcodes:

1 = SUMA, 2 = RESTA, 3 = MULTIPLICA, 4 = DIVIDE.

Supongamos que queremos realizar la operación 5 * 3 + 2, en la calculadora descrita arriba. En memoria, podríamos "escribir" el programa de la siguiente forma:

Localidad Opcode Significado Comentario 0 5 5 En esta localidad, tenemos el primer número de la fórmula 1 3 * En esta localidad, tenemos el opcode que representa la multiplicación. 2 3 3 En esta localidad, tenemos el segundo número de la fórmula 3 1 + En esta localidad, tenemos el opcode que representa la suma. 4 2 2 En esta localidad, tenemos el último número de la fórmula

Podemos ver que con esta representación, es simple expresar las operaciones de las que es capaz el hardware (en este caso, nuestra calculadora imaginaria), en la memoria.

La descripción y uso de los opcodes es lo que llamamos lenguaje de máquina . Es decir, la lista de códigos que la máquina va a interpretar como instrucciones, describe las capacidades de programación que tenemos de ella; es el lenguaje más primitivo, depende directamente del hardware, y requiere del programador que conozca el funcionamiento de la máquina al más bajo nivel.

los lenguajes más primitivos fueron los lenguajes de máquina. Esto, ya que el hardware se desarrolló antes del software, y además cualquier software finalmente tiene que expresarse en el lenguaje que maneja el hardware.

La programación en esos momentos era sumamente tediosa, pues el programador tenía que "bajarse" al nivel de la máquina y decirle, paso a pasito, cada punto de la tarea que tenía que realizar. Además, debía expresarlo en forma numérica; y por supuesto, este proceso era propenso a errores, con lo que la productividad del programador era muy limitada. Sin embargo, hay que recordar que en estos momentos, simplemente aún no existía alternativa.

El primer gran avance que se dio, como ya se comentó, fue la abstracción dada por el Lenguaje Ensamblador, y con él, el nacimiento de las primeras herramientas automáticas para generar el

1

Page 2: Programacion en Java

código máquina. Esto redujo los errores triviales, como podía ser el número que correspondía a una operación, que son sumamente engorrosos y difíciles de detectar, pero fáciles de cometer. Sin embargo, aún aquí es fácil para el programador perderse y cometer errores de lógica, pues debe bajar al nivel de la forma en que trabaja el CPU, y entender bien todo lo que sucede dentro de él.

Con el desarrollo en los 50s y 60s de algoritmos de más elevado nivel, y el aumento de poder del hardware, empezaron a entrar al uso de computadoras científicos de otras ramas; ellos conocían mucho de Física, Química y otras ramas similares, pero no de Computación, y por supuesto, les era sumamente complicado trabajar con lenguaje Ensamblador en vez de fórmulas. Así, nació el concepto de Lenguaje de Alto Nivel, con el primer compilador de FORTRAN (FORmula TRANslation), que, como su nombre indica, inició como un "simple" esfuerzo de traducir un lenguaje de fórmulas, al lenguaje ensamblador y por consiguiente al lenguaje de máquina. A partir de FORTRAN, se han desarrollado innumerables lenguajes, que siguen el mismo concepto: buscar la mayor abstracción posible, y facilitar la vida al programador, aumentando la productividad, encargándose los compiladores o intérpretes de traducir el lenguaje de alto nivel, al lenguaje de computadora.

Hay que notar la existencia de lenguajes que combinan características de los de alto nivel y los de bajo nivel (es decir, Ensamblador). Mi ejemplo favorito es C: contiene estructuras de programación de alto nivel, y la facilidad de usar librerías que también son características de alto nivel; sin embargo, fue diseñado con muy pocas instrucciones, las cuales son sumamente sencillas, fáciles de traducir al lenguaje de la máquina; y requiere de un entendimiento apropiado de cómo funciona la máquina, el uso de la memoria, etcétera. Por ello, muchas personas consideramos a lenguajes como C (que fue diseñado para hacer sistemas operativos), lenguajes de nivel medio.

Java

El lenguaje de programación Java, fue diseñado por la compañía Sun Microsystems Inc, con el propósito de crear un lenguaje que pudiera funcionar en redes computacionales heterogéneas ( redes de computadoras formadas por más de un tipo de computadora, ya sean PC, MAC's, estaciones de trabajo, etc.),y que fuera independiente de la plataforma en la que se vaya a ejecutar. Esto significa que un programa de Java puede ejecutarse en cualquier máquina o plataforma. El lenguaje fue diseñado con las siguientes características en mente:

• Simple. Elimina la complejidad de los lenguajes como "C" y da paso al contexto de los lenguajes modernos orientados a objetos. Orientado a Objetos. La filosofía de programación orientada a objetos es diferente a la programación convencional.

• Familiar. Como la mayoría de los programadores están acostumbrados a programar en C o en C++, el sintaxis de Java es muy similar al de estos.

• Robusto. El sistema de Java maneja la memoria de la computadora por ti. No te tienes que preocupar por apuntadores, memoria que no se esté utilizando, etc. Java realiza todo esto sin necesidad de que uno se lo indique.

• Seguro. El sistema de Java tiene ciertas políticas que evitan se puedan codificar virus con este lenguaje. Existen muchas restricciones, especialmente para los applets, que limitan lo que se puede y no puede hacer con los recursos críticos de una computadora.

• Portable. Como el código compilado de Java (conocido como byte code) es interpretado, un programa compilado de Java puede ser utilizado por cualquier computadora que tenga implementado el interprete de Java.

• Independiente a la arquitectura. Al compilar un programa en Java, el código resultante un tipo de código binario conocido como byte code. Este códido es interpretado por diferentes computadoras de igual manera, solamente hay que implementar un intérprete para cada plataforma. De esa manera Java logra ser un lenguaje que no depende de una arquitectura computacional definida.

2

Page 3: Programacion en Java

• Multithreaded. Un lenguaje que soporta multiples threads es un lenguaje que puede ejecutar diferentes líneas de código al mismo tiempo.

• Interpretado. Java corre en máquina virtual, por lo tanto es interpretado. • Dinámico. Java no requiere que compiles todas las clases de un programa para que este

funcione. Si realizas una modificación a una clase Java se encarga de realizar un Dynamic Bynding o un Dynamic Loading para encontrar las clases.

Java puede funcionar como una aplicación sola o como un "applet", que es un pequeño programa hecho en Java. Los applets de Java se pueden "pegar" a una página de Web (HTML), y con esto puedes tener un programa que cualquier persona que tenga un browser compatible podrá usar.

Nota: Diferencia entre Java y CGI La diferencia es esencialmente simple, un CGI se ejecuta en el servidor mientras que un programa en Java se ejecuta en la máquina del usuario.

Java funciona de la siguiente manera: El compilador de Java deja el programa en un Pseudo-código (no es código maquinal) y luego el intérprete de Java ejecuta el programa (lo que se conoce como el "Java Virtual Machine"). Por eso Java es multiplataforma, existe un intérprete para cada máquina diferente. Nota: El código maquinal es el código binario que la computadora entiende y puede ejecutar.

Para entender bien como funciona un applet de Java vean el siguiente ejemplo:

1. Existe un código de Java en un servidor de Web. (Los códigos de Java se caracterizan por tener la extensión *.class).

2. Una persona en Internet, con un browser compatible con Java, realiza una conección al servidor.

3. El servidor envía el documento HTML y el código en Java (*.class). 4. En la computadora del usuario remoto llegan ambos, y la Máquina Virtual de Java, que

está en el browser, transforma el código Java en un código que entienda la máquina local y se ejecuta el programa dentro de la página de Web.

5. Si el usuario realiza otra conexión a otro URL o se sale del browser, el programa se deja de ejecutar y en la computadora no queda rastro de el.

3

Page 4: Programacion en Java

Ejemplo de tutorial de Java:

En Java hay tres tipos de comentarios:

// comentarios para una sola línea

/* comentarios de una o

más líneas

*/

/** comentario de documentación, de una o más líneas

*/

Los dos primeros tipos de comentarios son los que todo programador conoce y se utilizan del mismo modo. Los comentarios de documentación, colocados inmediatamente antes de una declaración (de variable o función), indican que ese comentario ha de ser colocado en la documentación que se genera automáticamente cuando se utiliza la herramienta de Java, javadoc.

4

Page 5: Programacion en Java

Dichos comentarios sirven como descripción del elemento declarado permitiendo generar una documentación de nuestras clases escrita al mismo tiempo que se genera el código.

En este tipo de comentario para documentación, se permite la introducción de algunos tokens o palabras clave, que harán que la información que les sigue aparezca de forma diferente al resto en la documentación.

Identificadores

Los identificadores nombran variables, funciones, clases y objetos; cualquier cosa que el programador necesite identificar o usar.

En Java, un identificador comienza con una letra, un subrayado (_) o un símbolo de dólar ($). Los siguientes caracteres pueden ser letras o dígitos. Se distinguen las mayúsculas de las minúsculas y no hay longitud máxima.

Serían identificadores válidos:

identificador

nombre_usuario

Nombre_Usuario

_variable_del_sistema

$transaccion

y su uso sería, por ejemplo:

int contador_principal;

char _lista_de_ficheros;

float $cantidad_en_Ptas;

C

C es un lenguaje de programación diseñado por Dennis Ritchie, de los Laboratorios Bell, y se instaló en un PDP-11 en 1972; se diseñó para ser el lenguaje de los Sistemas Operativos UNIX1. A su vez, UNIX es un Sistema Operativo desarrollado por Ken Thompson, quién utilizó el lenguaje ensamblador y un lenguaje llamado B para producir las versiones originales de UNIX, en 1970. C se inventó para superar las limitaciones de B.

C es un lenguaje maduro de propósitos generales que se desarrolló a partir de estas raíces; su definición aparece en 1978 en el apéndice ``C Reference Manual'' del libro The C Programming Language, de Brian W. Kernighan y Dennis M. Ritchie (Englewood Cliffs, Nueva Jersey, Prentice-Hall 1978), pero el estándar recomendable más reciente apareció en junio de 1983, en el documento de los Laboratorios Bell titulado The C Programming Language-Reference Manual, escrito por Dennis M. Ritchie.

Un programa en C

5

Page 6: Programacion en Java

Generalizando, un programa en C consta de tres secciones. La primera sección es donde van todos los ``headers''. Estos ``headers'' son comúnmente los ``#define'' y los ``#include''. Como segunda sección se tienen las ``funciones''. Al igual que Pascal, en C todas las funciones que se van a ocupar en el programa deben ir antes que la función principal (main()). Declarando las funciones a ocupar al principio del programa, se logra que la función principal esté antes que el resto de las funciones. Ahora, solo se habla de funciones ya que en C no existen los procedimientos.

Y como última sección se tiene a la función principal, llamada main. Cuando se ejecuta el programa, lo primero que se ejecuta es esta función, y de ahí sigue el resto del programa.

Los símbolos { y } indican ``begin'' y ``end'' respectivamente. Si en una función o en un ciclo while, por ejemplo, su contenido es de solamente una línea, no es necesario usar ``llaves'' ({ }), en caso contrario es obligación usarlos.

Ejemplo de un programa en C

/*Programa ejemplo que despliega el contenido de "ROL" en pantalla*/

#include <stdio.h>

#define ROL "9274002-1"

despliega_rol() {

printf("Mi rol es : \%s\n", ROL);

}

void main() {

despliega_rol();

}

/* Fin programa */

Pascal

Pascal es un lenguaje de programación de alto nivel de propósito general; esto es, se puede utilizar para escribir programas para fines científicos y comerciales.

El lenguaje de programación Pascal fue desarrollado por el profesor Niklaus (Nicolás) Wirth en Zurich, Zuiza, al final de los años 1960s y principios de los 70s. Wirth diseñó este lenguaje para que fuese un buen primer lenguaje de programación para personas comenzando a aprender a programar. Pascal tiene un número relativamente pequeño de conceptos para aprender y dominar. Su diseño facilita escribir programas usando un estilo que está generalmente aceptado como práctica estándar de programación buena. Otra de las metas del diseño de Wirth era la implementación fácil. Él diseñó un lenguaje para el cual fuese fácil escribir un compilador para un nuevo tipo de computadora.

program Sorting;

6

Page 7: Programacion en Java

{ Este programa lee un natural y una secuencia de N caracteres de la entrada estandar; construye un indice para ordenarlos de menor a mayor e imprime en la salida la secuencia ordenada. }

uses CRT;

Const Max = 10;

Espacio = ' ';

Enter = chr (13);

type Indice = 1..Max;

Cantidad= 0..Max;

SecOfChar = record

elems : array [Indice] of char;

ult : Cantidad;

end;

SecOfInd = record

elems : array [Indice] of Indice;

ult : Cantidad;

end;

Natural = 0..MaxInt;

function PosMin (idx: SecOfInd; i: Indice; s: SecOfChar): Cantidad;

{ Devuelve la posicion en el indice idx del menor caracter en s, para las posiciones >= i. }

var j: Indice;

pm: Cantidad;

begin

if i > idx.ult then

pm := 0

else begin

7

Page 8: Programacion en Java

pm := i;

for j := i+1 to idx.ult do

if s.elems[idx.elems[j]] < s.elems[idx.elems[pm]] then

pm := j;

end;

PosMin := pm;

end;

procedure Swap (var idx: SecOfInd; i,j: Indice);

{ Intercambia las posiciones i j en idx. }

var tmp: Indice;

begin

if (i<=idx.ult) and (j<=idx.ult) then begin

tmp := idx.elems[i];

idx.elems[i] := idx.elems[j];

idx.elems[j] := tmp;

end;

end;

procedure InicInds (var idx: SecOfInd; cant: Indice);

{ Construye la secuencia de indices 1,2,3,...,n. Sera el indice inicial para el ordenamiento de una secuencia de caracteres c1,c2,...,cn. }

var n: Natural;

begin

n := cant;

idx.ult := n;

while n > 0 do begin

idx.elems [n] := n;

8

Page 9: Programacion en Java

n := n-1;

end;

end;

procedure InicSecChar (var s: SecOfChar);

{ Devuelve la secuencia vacia. }

begin

s.ult := 0;

end;

function Llena (s: SecOfChar): Boolean;

begin

Llena := s.ult = Max;

end;

{ PRE: not Llena(s) }

procedure InsCar (var s: SecOfChar; c: char);

{ Inserta el caracter c en la secuencia s }

begin

s.ult := s.ult + 1;

s.elems [s.ult] := c;

end;

procedure IndSelSort (s: SecOfChar; var ind: SecOfInd);

{ Construye el indice que ordena la secuencia s. Ordena el indice inicial 1,2, ..., n por el metodo de selection sort }

var i: Indice;

begin

InicInds (ind, s.ult);

for i := 1 to ind.ult-1 do begin

9

Page 10: Programacion en Java

Swap (ind, i, PosMin (ind, i, s));

end

end;

procedure WriteSorted (idx: SecOfInd; s: SecOfChar);

{ Imprime en la salida estandar la secuencia s ordenada segun el indice idx }

var i: Indice;

begin

write ('Ordenado: ');

for i := 1 to idx.ult do

write (s.elems[idx.elems[i]],' ');

writeln;

end;

procedure LeerCar (var c: char; var ok: boolean; sep: Char);

{ Lee de la entrada estandar un caracter seguido del caracter sep }

var c1, c2: char;

begin

c := ReadKey; write (c);

c1 := ReadKey; write (c1);

ok := c1 = sep;

end;

procedure LeerSecOfChar (var s: SecOfChar; cant: Natural; var ok: Boolean);

{ Construye una secuencia de cant caracteres provistos por el procedimeinto LeerCar. Si cant > Max trunca. }

var bien: Boolean;

i: Natural;

10

Page 11: Programacion en Java

ch, sep: Char;

begin

writeln ('Ingrese ',cant, ' caracteres separados por blancos. Enter para terminar ');

write (' > ');

InicSecChar (s);

i := 1;

ok := true;

sep := Espacio;

while ok and (i <= cant) and not Llena (s) do begin

if i = cant then sep := Enter;

LeerCar (ch, bien, sep);

i := i+1;

ok := ok and bien;

if ok then

InsCar (s, ch);

end;

end;

procedure LeerCant (var n: Natural);

{ Lee de la entrada estandar un natural <= Max }

begin

repeat

writeln ('Ingrese cantidad de caracteres (<=',Max,')');

write (' > ');

readln (n);

until n <= Max;

11

Page 12: Programacion en Java

end;

procedure Continuar (var seguir: Boolean);

var car: Char;

begin

writeln;

writeln ('Otro ? (s/n)');

write (' > ');

car := ReadKey;

writeln (car);

seguir := car in ['s','S'];

end;

var cant: Natural;

cars: SecOfChar;

inds: SecOfInd;

seguir, ok: boolean;

begin

repeat

ClrScr;

LeerCant (cant);

LeerSecOfChar (cars, cant, ok);

if ok then begin

IndSelSort (cars, inds);

writeln;

WriteSorted (inds, cars);

end

else begin

12

Page 13: Programacion en Java

writeln;

writeln ('Error en los datos');

end;

Continuar (seguir);

until not seguir;

end.

QBasic

Qbasic es un lenguaje de alto nivel, el cual consiste en instrucciones que los humanos pueden relacionar y entender. El compilador de Qbasic se encarga de traducir el mismo a lenguaje de máquina.

Un programa es una secuencia de instrucciones. El proceso de ejecutar esas instrucciones se llama correr el programa. Los programas contienen las funciones de entrada, procesamiento y salida. La persona que resuelve problemas mediante escribir programas en la computadora se conoce como programador. Después de analizar el problema y desarrollar un plan para solucionarlo, escribe y prueba el programa que instruye a la computadora como llevar a cabo el plan. El procedimiento que realiza el programador se define como "problem solving". Pero es necesario especificar que un programador y un usuario no son lo mismo. Un usuario es cualquier persona que use el programa.

Ejemplo de qbasic, para hacer una calculadora

DIM total AS DOUBLE

DIM number AS DOUBLE

DIM secondNumber AS DOUBLE

DIM more AS STRING

DIM moreNumbers AS STRING

DIM operation AS STRING

total = 0

more = "y"

moreNumbers = "c"

CLS

WHILE more = "y"

INPUT "Enter the first number"; number

13

Page 14: Programacion en Java

total = number

WHILE moreNumbers = "c"

COLOR 14

PRINT "The total is:"; total

COLOR 7

PRINT "Select an operation"

COLOR 2

PRINT "(+)"

COLOR 5

PRINT "(-)"

COLOR 1

PRINT "(x)"

COLOR 4

INPUT "(/)"; operation

COLOR 7

CLS

IF operation = "+" THEN

REM where we do additions

PRINT "Enter the number to Add to"; total

INPUT secondNumber

total = secondNumber + total

COLOR 14

PRINT "The total is now:"; total

COLOR 7

ELSE

IF operation = "-" THEN

14

Page 15: Programacion en Java

REM subtraction

PRINT "Enter the number to Subtract from"; total

INPUT secondNumber

total = total - secondNumber

COLOR 14

PRINT "The total is now:"; total

COLOR 7

ELSE

IF operation = "x" THEN

REM multiplication

PRINT "Enter the number to Multiply"; total; "by"

INPUT secondNumber

total = secondNumber * total

REM * is the multiplication sign in programs

COLOR 14

PRINT "The total is now:"; total

COLOR 7

ELSE

IF operation = "/" THEN

REM division

PRINT "Enter the number to Divide"; total; "by"

INPUT secondNumber

IF secondNumber = 0 THEN

COLOR 4

PRINT "You cannot divide by zero"

COLOR 7

15

Page 16: Programacion en Java

ELSE

total = total / secondNumber

REM / is the division sign in programs

END IF

COLOR 14

PRINT "The total is now:"; total

COLOR 7

ELSE

PRINT "you must select an operation"

END IF

END IF

END IF

END IF

INPUT "Do you wish to continue (c) or start with new numbers

(n)";moreNumbers

CLS

WEND

COLOR 14

PRINT "The grand total is:"; total

COLOR 7

INPUT "Do you wish to make more calculations (y - n)"; more

moreNumbers = "c"

REM if we don't put "moreNumbers" back to y, it will always

REM come back to "Do you wish to make more calculations" and never REM ask

for numbers again

16

Page 17: Programacion en Java

REM (try it)

total = 0

REM if we don't reset the total to 0, it will just

REM keep on adding to the total

WEND

END

Ensamblador

Cuando abstraemos los opcodes y los sustituimos por una palabra que sea una clave de su significado, a la cual comúnmente se le conoce como mnemónico , tenemos el concepto de Lenguaje Ensamblador. Así, podemos definir simplemente al Lenguaje Ensamblador de la siguiente forma:

Lenguaje Ensamblador es la primera abstracción del Lenguaje de Máquina, consistente en asociar a los opcodes palabras clave que faciliten su uso por parte del programador

Como se puede ver, el Lenguaje Ensamblador es directamente traducible al Lenguaje de Máquina, y viceversa; simplemente, es una abstracción que facilita su uso para los seres humanos. Por otro lado, la computadora no entiende directamente al Lenguaje Ensamblador; es necesario traducirle a Lenguaje de Máquina. Originalmente, este proceso se hacía a mano, usando para ello hojas donde se escribían tablas de programa similares al ejemplo de la calculadora que vimos arriba . Pero, al ser tan directa la traducción, pronto aparecieron los programas Ensambladores, que son traductores que convierten el código fuente (en Lenguaje Ensamblador) a código objeto (es decir, a Lenguaje de Máquina).

Una característica que hay que resaltar, es que al depender estos lenguajes del hardware, hay un distinto Lenguaje de Máquina (y, por consiguiente, un distinto Lenguaje Ensamblador) para cada CPU. Por ejemplo, podemos mencionar tres lenguajes completamente diferentes, que sin embargo vienen de la aplicación de los conceptos anteriores:

1.Lenguaje Ensamblador de la familia Intel 80x86 2.Lenguaje Ensamblador de la familia Motorola 68000 3.Lenguaje Ensamblador del procesador POWER, usado en las IBM RS/6000.

Tenemos 3 fabricantes distintos, compitiendo entre sí y cada uno aplicando conceptos distintos en la manufactura de sus procesadores, su arquitectura y programación; todos estos aspectos, influyen en que el lenguaje de máquina y ensamblador cambie bastante.

Ventajas y desventajas del Lenguaje Ensamblador

Una vez que hemos visto la evolución de los lenguajes, cabe preguntarse: ¿En estos tiempos "modernos", para qué quiero el Lenguaje Ensamblador?

El proceso de evolución trajo consigo algunas desventajas, que ahora veremos como las ventajas de usar el Lenguaje Ensamblador, respecto a un lenguaje de alto nivel:

1.Velocidad

17

Page 18: Programacion en Java

2.Eficiencia de tamaño

3.Flexibilidad

Por otro lado, al ser un lenguaje más primitivo, el Ensamblador tiene ciertas desventajas respecto a los lenguajes de alto nivel:

1.Tiempo de programación 2.Programas fuente grandes 3.Peligro de afectar recursos inesperadamente 4.Falta de portabilidad

Velocidad

El proceso de traducción que realizan los intérpretes, implica un proceso de cómputo adicional al que el programador quiere realizar. Por ello, nos encontraremos con que un intérprete es siempre más lento que realizar la misma acción en Lenguaje Ensamblador, simplemente porque tiene el costo adicional de estar traduciendo el programa, cada vez que lo ejecutamos.

De ahí nacieron los compiladores, que son mucho más rápidos que los intérpretes, pues hacen la traducción una vez y dejan el código objeto, que ya es Lenguaje de Máquina, y se puede ejecutar muy rápidamente. Aunque el proceso de traducción es más complejo y costoso que el de ensamblar un programa, normalmente podemos despreciarlo, contra las ventajas de codificar el programa más rápidamente.

Sin embargo, la mayor parte de las veces, el código generado por un compilador es menos eficiente que el código equivalente que un programador escribiría. La razón es que el compilador no tiene tanta inteligencia, y requiere ser capaz de crear código genérico, que sirva tanto para un programa como para otro; en cambio, un programador humano puede aprovechar las características específicas del problema, reduciendo la generalidad pero al mismo tiempo, no desperdicia ninguna instrucción, no hace ningún proceso que no sea necesario.

Para darnos una idea, en una PC, y suponiendo que todos son buenos programadores, un programa para ordenar una lista tardará cerca de 20 veces más en Visual Basic (un intérprete), y 2 veces más en C (un compilador), que el equivalente en Ensamblador.

Por ello, cuando es crítica la velocidad del programa, Ensamblador se vuelve un candidato lógico como lenguaje.

Ahora bien, esto no es un absoluto; un programa bien hecho en C puede ser muchas veces más rápido que un programa mal hecho en Ensamblador; sigue siendo sumamente importante la elección apropiada de algoritmos y estructuras de datos. Por ello, se recomienda buscar optimizar primero estos aspectos, en el lenguaje que se desee, y solamente usar Ensamblador cuando se requiere más optimización y no se puede lograr por estos medios.

Tamaño

Por las mismas razones que vimos en el aspecto de velocidad, los compiladores e intérpretes generan más código máquina del necesario; por ello, el programa ejecutable crece. Así, cuando es importante reducir el tamaño del ejecutable, mejorando el uso de la memoria y teniendo también beneficios en velocidad, puede convenir usar el lenguaje Ensamblador. Entre los programas que es crítico el uso mínimo de memoria, tenemos a los virus y manejadores de dispositivos (drivers). Muchos de ellos, por supuesto, están escritos en lenguaje Ensamblador.

Flexibilidad

18

Page 19: Programacion en Java

Las razones anteriores son cuestión de grado: podemos hacer las cosas en otro lenguaje, pero queremos hacerlas más eficientemente. Pero todos los lenguajes de alto nivel tienen limitantes en el control; al hacer abstracciones, limitan su propia capacidad. Es decir, existen tareas que la máquina puede hacer, pero que un lenguaje de alto nivel no permite. Por ejemplo, en Visual Basic no es posible cambiar la resolución del monitor a medio programa; es una limitante, impuesta por la abstracción del GUI Windows. En cambio, en ensamblador es sumamente sencillo, pues tenemos el acceso directo al hardware del monitor.

Resumiendo, la flexibilidad consiste en reconocer el hecho de que

Todo lo que puede hacerse con una máquina, puede hacerse en el lenguaje ensamblador de esta máquina; los lenguajes de alto nivel tienen en una u otra forma limitantes para explotar al máximo los recursos de la máquina.

Tiempo de programación

Al ser de bajo nivel, el Lenguaje Ensamblador requiere más instrucciones para realizar el mismo proceso, en comparación con un lenguaje de alto nivel. Por otro lado, requiere de más cuidado por parte del programador, pues es propenso a que los errores de lógica se reflejen más fuertemente en la ejecución.

Por todo esto, es más lento el desarrollo de programas comparables en Lenguaje Ensamblador que en un lenguaje de alto nivel, pues el programador goza de una menor abstracción.

Programas fuente grandes

Por las mismas razones que aumenta el tiempo, crecen los programas fuentes; simplemente, requerimos más instrucciones primitivas para describir procesos equivalentes. Esto es una desventaja porque dificulta el mantenimiento de los programas, y nuevamente reduce la productividad de los programadores.

Peligro de afectar recursos inesperadamente

Tenemos la ventaja de que todo lo que se puede hacer en la máquina, se puede hacer con el Lenguaje Ensamblador (flexibilidad). El problema es que todo error que podamos cometer, o todo riesgo que podamos tener, podemos tenerlo también en este Lenguaje. Dicho de otra forma, tener mucho poder es útil pero también es peligroso.

En la vida práctica, afortunadamente no ocurre mucho; sin embargo, al programar en este lenguaje verán que es mucho más común que la máquina se "cuelgue", "bloquee" o "se le vaya el avión"; y que se reinicialize. ¿Por qué?, porque con este lenguaje es perfectamente posible (y sencillo) realizar secuencias de instrucciones inválidas, que normalmente no aparecen al usar un lenguaje de alto nivel.

En ciertos casos extremos, puede llegarse a sobreescribir información del CMOS de la máquina (no he visto efectos más riesgosos); pero, si no la conservamos, esto puede causar que dejemos de "ver" el disco duro, junto con toda su información.

Falta de portabilidad

Como ya se mencionó, existe un lenguaje ensamblador para cada máquina; por ello, evidentemente no es una selección apropiada de lenguaje cuando deseamos codificar en una máquina y luego llevar los programas a otros sistemas operativos o modelos de computadoras. Si

19

Page 20: Programacion en Java

bien esto es un problema general a todos los lenguajes, es mucho más notorio en ensamblador: yo puedo reutilizar un 90% o más del código que desarrollo en "C", en una PC, al llevarlo a una RS/6000 con UNIX, y lo mismo si después lo llevo a una Macintosh, siempre y cuando esté bien hecho y siga los estándares de "C", y los principios de la programación estructurada. En cambio, si escribimos el programa en Ensamblador de la PC, por bien que lo desarrollemos y muchos estándares que sigamos, tendremos prácticamente que reescribir el 100 % del código al llevarlo a UNIX, y otra vez lo mismo al llevarlo a Mac.

20

Page 21: Programacion en Java

Para familiarizarnos con el lenguaje C lo mejor es comenzar haciendo pequeños programas. El objetivo será dar una primera impresión de como trabaja el C, sin profundizar demasiado en todas sus características. En esta primera parte se abordarán los métodos que son comunes a todos los lenguajes de programación estructurada.

Los programas en C están formados por una serie de líneas de código que se ejecutan sucesivamente. Todos los programas se dividen en bloques. Cada bloque de código se encierra en funciones. La ejecución del programa siempre comienza en la función main(). Esta función es la encargada de llamar a las demás. Para escribir la función main se coloca al principio de la función el siguiente código:

main()

{

Luego escribimos todas las líneas de código. A cada línea de código le llamaremos sentencia. Después de cada sentencia escribiremos un ';'. En C todas las sentencias acaban con un . Hay varios tipos de sentencia. Las más comunes la llamada a una función. Cuando hace esto el programa llama a la función. Entonces se ejecuta el código de la función. Cuando la función finaliza la ejecución devuelve el control a la función main(). Las funciones son muy difíciles de reconocer, pues a continuación de su nombre van los paréntesis.

Para acabar el código de una función siempre escribiremos al principio de una nueva línea una llave de cierre '}'.

Por ejemplo:

#include <stdio.h>

main()

{

printf("hola, mundo\n");

}

En este programa la función main consta de una sola sentencia, que es la llamada a la función printf(). La función printf imprime en la salida habitual, que generalmente es el terminal en el que trabajamos, el mensaje que le demos. El mensaje que queremos imprimir es hola, mundo, seguido de un avance del cursor al principio de la línea siguiente. El mensaje lo debemos encerrar entre comillas ", para que el compilador sepa cual es su longitud.

La función printf() es una función que pertenece a la librería estándar del C. Esta librería tiene un conjunto muy amplio de funciones listas para usar, lo que nos evitar el trabajo de usarlas. Para

Programación en C

21

Page 22: Programacion en Java

usar esta librería debemos avisar al compilador. Por ello incluimos como primera línea del programa la línea #include <stdio.h>.

En esta librería hay un montón de funciones útiles. Para comenzar daremos una lista de alguna de ellas, con una pequeña explicación de lo que hacen, aunque no nos extenderemos en su uso. Cuando comencemos a usarlas rápidamente nos daremos cuenta de su uso. Estas funciones son:

printf(mensaje)

imprime un mensaje en el terminal

putchar(carácter)

escribe un carácter en el terminal

getchar()

recoge un carácter del terminal

scanf(variables)

lee variables del terminal

gets()

lee una línea del terminal

De estas funciones, printf() y scanf() son las mas complicadas de usar, y las que admiten más posibilidades de funcionamiento. De echo son unas de las mas complicadas de la librería estándar.

Estructura de un programa en C

Un programa en C es simplemente un fichero de caracteres que contiene un conjunto de instrucciones que un programa especial, el compilador o traductor, se encargar de transformar en un programa que la computadora pueda ejecutar. Un programa normalmente suele estar compuesto de tres partes:

la sección de variables, que especifica los datos y sus tipos que vamos a manejar a lo largo del programa.

la función principal, que se suele llamar "main", que ser la que defina la estructura del programa.

las funciones o subrutinas auxiliares, que son llamados por la rutina principal, la función main. Estas subrutinas se suelen colocar después de la función main.

Cuando la envergadura del programa es grande se suele fragmentar el programa en varias partes, incluyendo cada parte en un fichero separado. El lenguaje C define el m‚todo que debemos seguir para separar las diferentes partes del programa. Normalmente colocaremos en cada fichero todas las subrutinas y funciones que se encarguen de una tarea del programa. Así la función main ir "llamando" a las subrutinas a medida que las vaya necesitando.

Un primer vistazo a la programación estructurada: las funciones

22

Page 23: Programacion en Java

Como hemos visto en C los programas constan de una rutina principal, de nombre main y una serie de subrutinas asociadas. En C las rutinas se crean mediante una función. Una función es un fragmento de código que realiza una tarea. En C las funciones siempre tienen un nombre, que viene dado por un identificador. Por ejemplo main es el identificador de la función main, que es la rutina principal de todo programa en C. Para escribir la función main solo tenemos que colocar al principio de una línea su identificador, seguido de los caracteres '(' y ')'. Es costumbre entre los programadores de C de escribir el primer paréntesis pegado al identificador de la función, ya que facilita enormemente su clasificación como función. En realidad esto no es necesario ya que el compilador salta los espacios automáticamente. A continuación del ultimo paréntesis se escribe el carácter de abrir llaves '{' . También es costumbre entre los programadores el escribir esta llave en la línea siguiente, y no a continuación del paréntesis, aunque al compilador le da igual, y no protestar si introducimos o quitamos espacios en blanco. Recordemos que decíamos que el C es un lenguaje de formato libre. A continuación de la llave se escribe el código de la función. Por último, al principio de una nueva línea debemos incluir otra llave '}', que servir de cierre de nuestra función. Un ejemplo de función main puede ser:

main()

{

printf("Hola, mundo\n");

}

Esta función casi constituye un programa completo, salvo que al intentar compilarlo el compilador se puede quejar y decirnos que no conoce el identificador printf. Si repasamos nuestra lista de palabras reservadas veremos que no aparecen ni la palabra main ni la palabra printf, sin embargo el sistema sólo se quejar probablemente de la palabra printf. Esto se debe a que la palabra main suele estar predefinida como función en la mayoría de los sistemas en C. La función printf simplemente imprime en la salida del sistema (generalmente la pantalla del usuario) la frase que le demos. La función printf forma parte de lo que se conoce como librería estándar del C. Con casi todos los compiladores se suele incluir un conjunto de funciones predefinidas que realizan las funciones mas usuales de los lenguajes de programación: entrada y salida de datos por pantalla, creación y manipulación de ficheros y otras muchas funciones. En esto el lenguaje C es muy potente, pues la librería estándar es muy amplia.

Para avisar al compilador que vamos a usar la función printf simplemente debemos añadir una línea al principio de nuestro programa, quedando ahora el programa como sigue:

#include <stdio.h>

main()

{

printf("Hola, mundo\n");

}

Este es ya un programa completo en C, uno de los más simples que se pueden crear. Simplemente escribe un mensaje de salida en nuestro terminal. El significado de sus partes se ir viendo más adelante.

23

Page 24: Programacion en Java

El código de un programa en C

El C es un lenguaje de formato libre. Esto quiere decir que consiste en que un programa está formado por comandos que están separados por espacios en blanco y también. Para ello el C considera como espacios en blanco, no sólo el carácter blanco ' ', sino también el carácter de tabulador '\t' y el carácter de nueva línea '\n' o '\r'. El numero de espacios que empleemos no varía el significado del programa. Aunque al compilador del lenguaje le da igual el número de espacios en blanco que insertemos entre los comandos, por motivos de legibilidad seguiremos una serie de normas. La primera de ellas hace referencia a los comentarios.

Un comentario es una línea que se incluye en el programa, cuya misión consiste en aclarar la función de una parte concreta del programa a otro lector, o incluso al mismo programador. En C hay dos formas de incluir estos comentarios. La primera es incluir el texto que sirve de comentario al principio de la sección, entre dos símbolos especiales: el /* o principio de comentario y el */ o fin de comentario. Todo el texto que incluyamos entre ellos el compilador lo ignorar , incluyendo los saltos de línea. Por ejemplo si una sección del programa se encarga de ofrecer los resultados finales del programa, podríamos incluir en el código el siguiente comentario:

/* esta sección se encarga de imprimir los datos en la impresora asignada */

Y aquí iría el resto del programa.

El otro tipo de comentarios se suele usar para señalar una determinada línea del programa. Para ello escribimos el comentario a la derecha de la línea a comenta. Por ejemplo, si en una línea aparece el comando gets(), podríamos poner a la derecha en un comentario lo que hace:

gets(linea); /* recoge una línea del teclado */

Este tipo de comentario es el que usaremos en muchas de la explicaciones de ahora en adelante.

La sección de variables globales

Normalmente en todo programa en C hay una sección de variables globales. En las variables globales almacenaremos datos que deben ser accesibles a todo el programa. Cuando el programa es pequeño, por ejemplo si consta de un sólo fichero, por comodidad se suelen definir todas las variables como globales. Esto tiene como ventaja que no deberemos definir el tipo de las variables en las funciones auxiliares, ya que ser n directamente visibles para ellas. Como inconveniente tenemos que al ser las variables globales accesibles por todas las funciones podremos modificarlas por error, por lo que pueden aparecer errores difíciles de depurar. La mejor estrategia es dejar como variables globales sólo las estrictamente necesarias, definiendo en cada función las variables que necesitemos.

La función main

Si el programa es pequeño es probable que la mayor parte del programa se halle dentro de la función main. Cuando el programa comienza a tener un tamaño mayor conviene dejar para la función main sólo el cuerpo del programa. Al principio de la función main colocaremos todas las rutinas de inicialización. Si vamos a manejar algún fichero a lo largo del programa la función main es un buen lugar para abrirlo. También se suele procesar en la función main los mensajes de bienvenida, en los que se muestra el nombre del autor y demás información. Si el programa admite par metros en la línea de órdenes, la función main debe procesarlos, ya que la función main tiene normalmente acceso a la línea de argumentos con que fue ejecutado el programa.

24

Page 25: Programacion en Java

Las variables

El siguiente paso para programar en C consiste en presentar las variables. Las variables permiten guardar información. Los principales tipos de datos son los datos numéricos, los caracteres y las cadenas de caracteres. En principio situaremos las variables al principio del programa.

Para utilizar una variable situaremos antes de la función main el nombre de la variable precedido de su tipo. En principio vamos a usar tres tipos de variables: los enteros, cuyo especificador de tipos es la palabra reservada int, los caracteres, cuya palabra reservada es char, y las cadenas de caracteres, cuyo especificador de tipo es char *. Los enteros son cualquier número positivo o negativo sin decimal, de digamos "tamaño pequeño". Los caracteres son letras, números, signos de puntuación y algún carácter especial, como el fin de línea y la campanilla. Las cadenas de caracteres son un conjunto de caracteres, de cualquier longitud encerado entre comillas dobles".

Comencemos creando un programa con una variable numérica, numero1:

#include <stdio.h>

int numero1 = 1;

main()

{

printf("numero1 vale %d\n", numero1);

}

Como siempre, haremos el programa siguiendo la estructura que hemos seguido. Observar que al crear la variable le hemos colocado un = 1. Esto permite almacenar un 1 en nuestra variable. Luego dentro de la función main salo tenemos que llamar a printf() para imprimir nuestra variable. Aquíí ya empezamos a ver complicaciones en el uso de printf(). Printf coge nuestra cadena de caracteres numero1 vale %d\n. El %d indica que printf debe insertar en el mensaje el valor de la variable que a continuación le demos, en nuestro caso numero1. El carácter \n es un carácter especial, el de nueva línea, y le dice a printf() que debe avanzar el cursor al principio de la línea siguiente.

El valor de las variables es, como su propio nombre indica, variable. Podemos alterar su valor en cualquier punto del programa. La forma mas sencilla de hacerlo es mediante una sentencia de asignación. Para asignar un nombre a una variable se escribe su identificador seguido de un = y el nuevo valor. Este tipo de sentencia es el segundo más importante. Así:

#include <stdio.h>

int i = 1;

main()

{

printf("el antiguo valor de i es %d\n", i);

i = 2;

25

Page 26: Programacion en Java

printf("el nuevo es %d\n", i);

}

El lado derecho de una asignación es una expresión. Una expresión es otro tipo de sentencia, quizá s el tercero más importante. Una expresión es una sentencia que al ser evaluada devuelve un valor. Los tipos de expresiones más frecuentes son las operaciones aritméticas: suma, resta, multiplicaciones y divisiones. Para escribir una expresión aritmética usaremos una notación similar a la usada en las calculadoras, incluyendo el uso de paréntesis. Por ejemplo:

#include<stdio.h>

int i;

main()

{

i = 2 + 2;

printf(" 2 + 2 = %d\n" , i);

}

Otros ejemplos de operaciones aritméticas son:

i = 2 * 2 + 6;

i = 2 * (2+ 6);

i = 1 - 4 / 2;

Observar que el * y el / tienen mayor "precedencia" que el + y el -, es decir al evaluar la expresión se evalúa antes un * y un - que un + o un -.

El tema de la evaluación de expresiones es mas complejo, y lo dejaremos para un capítulo mas avanzado.

Además contamos con otro tipo de expresiones, el resultado de llamar a una función. Lo veremos mejor con el tipo de variable carácter. Las variables de tipo carácter se emplean de modo análogo a las de tipo entero, salvo que ahora almacenan un carácter. En el siguiente ejemplo crearemos una variable y después de imprimirla la asignaremos el resultado de una expresión del tipo llamada a función. Utilizaremos la función getchar() que devuelve el siguiente carácter disponible desde el terminal.

#include <stdio.h>

char c = 'a' ;

main()

{

26

Page 27: Programacion en Java

putchar(c);

putchar('\n');

printf("introduce un carácter por el terminal\n");

c = getchar();

printf("el primer carácter que has ");

printf("introducido es: %c\n", c);

}

Observar que el modificador para imprimir un carácter con printf() es %c.

Si sustituimos este modificador por el %d se imprimir el código ASCII del carácter introducido.

Aquí ya podemos observar una característica muy importante del C, que es la equivalencia de tipos. El tipo de datos entero y el tipo de datos carácter son intercambiables, es decir, si una expresión espera un entero y le damos un carácter y le damos un entero, el compilador promocionar el carácter a entero. Esto siempre es posible. El proceso inverso también se puede hacer. Si a una variable de tipo carácter y le damos un entero el compilador tratar de meterlo como pueda. Si el entero es pequeño esto se hará sin problemas. Si el entero es grande, el compilador truncar el entero, es decir, le cortar un trozo. El proceso de truncado se ve mejor usando representación binaria. Los caracteres se suelen representar mediante bytes, es decir 8 bits. Los enteros suelen ser dos o cuatro bytes. El compilador para pasar de un carácter a entero simplemente le añade un byte a cero. Para pasar de entero a carácter el compilador se olvida de todos los bytes salvo el de menor orden. Por eso la conversión entre tipos funciona la mayoría de las ocasiones.

Así la función getchar() devuelve un entero, no un carácter. Esto se hace así por otro motivo un poco más oscuro, relacionado con el fin de fichero, que explicaremos al tratar el tema de los ficheros. No obstante, la función getchar() funciona sin complicaciones. Así mismo la función putchar() admite un carácter o un entero, pero siempre los imprime como carácter. Cadenas de caracteres y arreglos.

Las cadenas de caracteres nos permiten manejar texto con facilidad. Ya hemos visto que la función printf nos permite sacar una cadena por pantalla. Esto nos hace pensar que las cadenas de caracteres pueden servir para almacenar tiras de caracteres, como nombres o texto. Sin embargo poseen una pega: son constantes, por lo que no se pueden alterar.

Para almacenar grupos de caracteres utilizaremos los arreglos de caracteres.

Identificadores y palabras reservadas

El lenguaje C está formado por un conjunto pequeño de palabras clave o comandos y una serie de operadores. Hay cerca de 40 palabras claves, frente a las 150 del BASIC o 200 que poseen otros lenguajes, como el COBOL y el PASCAL. Estas palabras son:

auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct typedef union unsigned void volatile while

27

Page 28: Programacion en Java

A este conjunto de palabras se les denomina "palabras reservadas".

Cuando en el C creemos nuestras propias subrutinas y conjuntos de datos les deberemos poner nombres. Para nombrar las funciones (subrutinas) y variables (datos) utilizaremos los identificadores. Un identificador es un conjunto de caracteres alfanuméricos (letras y números) que comienzan siempre por una letra. Para formar un identificador usaremos indiferentemente mayúsculas y minúsculas, números y también el carácter '_' (subrayado), aunque este carácter tiene un significado especial, por lo que en principio debemos evitar su uso. Como identificador vale cualquier secuencia de letras y números, de cualquier longitud, pero con dos limitaciones:

no podemos empezar con un número

las palabras reservadas no se pueden usar como identificador, aunque un identificador puede comenzar con una palabra reservada.

De aquí la denominación de palabras reservadas. Ejemplos de identificadores validos serían:

hola

hola127

printf

whiledo

Variables

Una variable es un lugar donde se puede almacenar temporalmente un dato. En C las variables tienen un nombre que las identifica, y sirve para hacer referencia a ellas. También tienen un tipo, que es el tipo de datos que puede almacenar. Por ejemplo podemos tener una variable de tipo entero de nombre num. Observar que para dar un nombre a una variable tenemos que usar un identificador. Para crear una variable en un lugar determinado del un programa escribiremos primero el tipo de variable y luego el identificador con el que queremos nombrar la variable, seguido todo de un ';'. Por ejemplo:

int numero; /* crea la variable numero de */

/* tipo entero */

char caracter1; /* crea una variable de tipo caracter1 */

las variables se pueden inicializar, es decir, darles un valor inicial, en el momento de creación. Para ello detrás del identificador ponemos el caracter1 '=' seguido del valor inicial. Los valores iniciales pueden ser cualquier constante válida para el tipo de variable que creemos. Por ejemplo:

int numero = 0; /*crea la variable entera numero */

/* y la inicializa a 0*/

char c = 'a'; /* crea una variable caracter1 y */

28

Page 29: Programacion en Java

/* la inicializa a 'a' */

Duración de las variables

Las variables pueden ser de dos tipos: estáticas y dinámicas. Las estáticas se crean al principio del programa y duran mientras el programa se ejecute.

Las variables son dinámicas si son creadas dentro de una función. Su existencia está ligada a la existencia de la función. Se crean cuando la función es llamada y se destruyen cuando la función o subrutina devuelve el control a la rutina que la llamó.

Las variables estáticas se utilizan para almacenar valores que se van a necesitar a lo largo de todo el programa. las variables dinámicas se suelen utilizar para guardar resultados intermedios en los cálculos de las funciones.

Como regla general una variable es estática cuando se crea fuera de una función y es dinámica cuando se crea dentro de una función.

Por ejemplo en el siguiente programa :

#include <stdio.h>

int numero1 = 1;

main()

{

int numero2 = 2;

printf("%d, %d\n", numero1, numero2);

}

hemos creado la variable estática numero1, que dura todo el programa, y la variable numero2, que dura sólo mientras se ejecuta la función main(). En este programa tan pequeño , la función main() es la que ocupa todo el tiempo de ejecución, por lo que no apreciaremos diferencia en el uso de ambas, aunque más adelante si se ver su uso.

Alcance de las variables

Otra característica de las variables es su alcance. El alcance se refiere a los lugares de un programa en los que podemos utilizar una determinada variable. Distinguiremos así dos tipos principales de variables: globales y locales. Una variable es global cuando es accesible desde todo el programa, y es local cuando solo puede acceder a ella la función que la creo. También hay una norma general para el alcance de las variables: una variable es global cuando se define fuera de una función, y es local cuando se define dentro de una función. En nuestro ejemplo anterior numero1 es una variable global y numero2 es una variable local.

Dentro de las variables globales hay dos tipos: las que son accesibles por todos los ficheros que componen nuestro programa y las que son accesibles solo por todas las funciones que componen un fichero. Esto es debido a que normalmente los programas en C se fragmentan en módulos más

29

Page 30: Programacion en Java

pequeños, que son mas fáciles de manejar y depurar. Por ello hay veces que nos interesar que una variable sea accesible desde todos los módulos, y otras solo queremos que sea accesible por las funciones que componen un determinado modulo. Por defecto todas las variables globales que creemos son accesibles por todos los ficheros que componen nuestro programa.

Modificadores de tipo

Podemos fácilmente modificar el alcance y la duración de una variable que tiene asignado por defecto: Esto es una operación muy común y útil. Para hacerlo antepondremos al tipo de la variable un modificador, que es una palabra reservada, que cambiar estas característica.

El primer modificador es la palabra clave static. Cuando a una variable local se le añade el modificador static pasa de ser dinámica a ser estática. Así la duración de la variable se amplía a la duración del programa completo. Observar que una variable estática solo se crea una vez, al principio del programa, por lo que la inicialización solo se produce una vez para una variable estática.

Además el modificador static tiene otro uso. Si añadimos este modificador a una variable global, definida fuera de una función, entonces modificamos su alcance: pasa de tener alcance global a todos los ficheros del programa a ser solo accesible por las funciones del fichero en el que se crea.

Otro modificador usual es externa. Este modificador se usa cuando una variable que se creo en otro modulo se quiere usar en un modulo. Cuando añadimos a la variable este modificador el compilador queda advertido de que la variable ya existe en otro modulo, por lo que el compilador no tiene que crearla, sino simplemente usarla. Entonces a este tipo de proceso se le llama declaración de tipo de variable. Por ejemplo:

extern int numero;

main()

{

printf("%d\n", numero);

}

es un programa en que declaramos la variable externa numero, que habremos creado en otro modulo.

Una diferencia muy importante entre una definición y una declaración es que en la definición no se reserva espacio en la memoria para la variable, y en la definición si se crea.

Hay otros modificadores que no son tan usuales: auto, volatile y register. El modificador auto indica que una variable local es dinámica (en la terminología del C automática). Observar que por defecto las variables locales a una función son automáticas, por lo que no se usa. Sin embargo todos los compiladores la reconocen y no protestan si la usamos. Por ejemplo:

main()

{

auto numero = 1;

30

Page 31: Programacion en Java

printf("%d\n", numero);

}

crea una variable automática entera. Si quitamos auto el programa no se diferencia.

El modificador register se usa más a menudo, sobre todo en la llamada "programación de sistemas". Recordemos que el C fue creado para este tipo de programación. Este modificador le pide al compilador que almacene la variable en un registro de la maquina, que es el lugar más eficiente para guardar las variables. Esto se hace porque el trabajo con los registros del procesador es mucho más r pido que el trabajo con la memoria central. Hay dos detalles importantes: normalmente no hay muchos registros libres en el procesador, pues el compilador los usa para otros propósitos. Entonces el modificador register es mas bien un ruego que una orden. Otro aspecto es que muchos compiladores realizan trabajos de optimización, que son modificaciones en el código que generan que hace trabajar al programa más deprisa. Aun así, en rutinas críticas, que deben ejecutarse deprisa se suele usar.

El modificador volatile se usa para decirle que el contenido de la variable puede ser modificado en cualquier momento desde el exterior de nuestro programa, sin que podamos evitarlo. Lo que hace el modificador es instruir al compilador para que lea de nuevo el valor de la variable de la memoria cuando tenga que usarlo. Este modificador evita que el compilador no genere código para optimizar la variable. Evidentemente el uso de volatile excluye el uso de register y viceversa.

Ambos modificadores, register y volátiles se emplean de manera análoga al modificador auto.

Tipos de datos en C

Un programa normalmente lo que hace es procesar un conjunto de datos para obtener alguna conclusión de ellos. Pos ejemplo un programa de estadísticas recoge una serie de datos y elabora gráficas que nos ayudan a tomar decisiones. En C los datos de una aplicación se representa haciendo uso de un conjunto de datos predefinidos en el lenguaje.

Distinguiremos para empezar tres tipos de datos fundamentales: caracteres, enteros y cadenas de caracteres (en ingles "strings"). El tipo de datos carácter consiste de un único carácter y se suele representar por su carácter en código ASCII situado entre apóstrofes. Por ejemplo:

'p' /* la letra p minúscula */

'1' /* el numero 1 */

' ' /* el carácter en blanco */

Hay otras formas de representar caracteres, que se emplean cuando es un carácter que no se puede introducir directamente desde el teclado. Para ello debemos conocer su código ASCII. Para representar el carácter de numero ascii 27, (el código para el carácter ESCAPE), basta colocar el numero ascii en el sistema octal precedido de la barra atrás, y todo ello entre apóstrofes, tal y como hacemos para los demás caracteres:

'\27**' /* representa el código ESCAPE, de ASCII 27 */

En C hay algunos caracteres especiales que se usan frecuentemente. Estos caracteres tiene una representación especial. Algunos de ellos son:

31

Page 32: Programacion en Java

'\n' /* carácter nueva línea */

'\r' /* retorno de carro (carriage return) */

'\t' /* tabulador */

El siguiente tipo de datos es el entero. Consiste en un número sin parte decimal, aunque puede tener signo. Generalmente con el tipo de datos entero no representamos números muy grandes. son ejemplos:

0

124

-2000

El tipo de datos entero nos permitir hacer operaciones aritméticas, como la suma y la multiplicación. El tipo de datos entero es quizás el más importante de todos los tipos de datos, y muchas veces es el tipo d datos por defecto, es decir, cuando el compilador se encuentre con un dato y no le hayamos dicho cual es su tipo supondrá que es un entero. Esto se ve claramente con las funciones, ya que siempre devuelven un valor, que por defecto es un entero.

El último tipo de datos importante en C es la cadena de caracteres. Está formada por un conjunto de caracteres encerrados entre comillas. Podemos usar todos los caracteres del conjunto ASCII, incluso los especiales. Los caracteres normales se incluyen entre las comillas tal cual, sin necesidad de apóstrofes, y los especiales se incluyen utilizando la representación del C. Por ejemplo:

"Hola, mundo\n"

En este ejemplo observamos la cadena de caracteres "Hola, mundo", a la que hemos añadido un carácter de retorno de carro al final. El motivo de ello es que más adelante cuando la imprimamos el carácter de retorno de carro \n actuar como un comando que obligar al cursor a avanzar una línea y situarse al principio de la siguiente.

Los tipos de datos carácter, entero y cadena no son los únicos que posee el lenguaje C, pero si los mas importantes. Como avance podemos decir que también posee el tipo de datos en coma flotante, que es el que usan las calculadoras científicas, varios tipos da datos enteros, de diferente tamaño, el tipo de datos entero sin signo, un tipo de datos científico de mayor precisión, y otros tipos de datos que sirven para fabricarnos un tipo de datos a medida de nuestra necesidad: arreglos, estructuras y uniones. Pero esto es un tema más avanzado.

Tipos de enteros

El tipo de datos entero admite varias variantes, que le permiten representar cantidades más grandes. Aunque el tamaño de los enteros depende de la implementación con la que estemos trabajando, suele ser habitual que tenga un tamaño de 16 bits. Por ello el entero más grande que se puede representar suele estar en torno al 32000. Cuando queremos alcanzar un número mas alto y no nos importa el signo le podemos pedir al compilador que el tipo de datos sea "sin signo". Esto se hace con la palabra reservada unsigned. Así el tipo de datos entero sin signo se comporta como un nuevo tipo de datos. Podemos definir variables del tipo unsigned int, al igual que lo hacíamos con enteros normales. Normalmente cuando nos referimos al tipo de datos sin signo nos estamos refiriendo al tipo entero sin signo.

32

Page 33: Programacion en Java

Por ello cuando definamos un entero sin signo no hará falta escribir la palabra reservada int. Un ejemplo de definición con inicialización de un unsigned es:

unsigned u = 40000;

Podríamos también haber escrito unsigned int, pero no suele ser habitual. Si trabajamos con enteros de 16 bits, el número 40000 no cabria en un entero normal.

Otro tipo de datos útil es el entero largo, que análogamente al entero sin signo se representa con la palabra reservada long. Las palabras long y unsigned son modificadores de tipo, ya que actúan sobre uno de los tipos incorporados. Por ejemplo:

long largo = 100000;

define una variable de tipo entero largo. Hay más modificadores de tipo. El modificador short hace referencia a un entero corto, de pequeño tamaño. Hay que tener en cuenta que el tamaño de cada tipo de datos depende del sistema.

En C estándar un entero corto tiene al menos 16 bits. Muchas veces el entero corto y el entero coinciden. En los sistemas basados en microprocesadores de 32 bits suele ser frecuente encontrar enteros de 32 bits de anchos, por lo que no coinciden el entero corto y el entero.

Además también podemos definir enteros cortos y largos sin signo. Por ejemplo:

unsigned short int u = 1;

unsigned long l = 1000000;

El signo de los caracteres

Dependiendo del sistema el tipo de datos carácter puede tener signo o no.

Normalmente es una cuestión que no posee relevancia, pero por si necesitásemos que el tipo de datos carácter lleve o no signo, podemos aplicarle dos modificadores: unsigned para caracteres sin signo, y signed para caracteres con signo.

Tipos de datos en coma flotante

Para representar números decimales el C posee dos tipos de datos: números en coma flotante con simple y doble precisión. El tipo de datos float corresponde al número de simple precisión. El tipo double representa a los de doble precisión.

Expresiones

Las expresiones son sentencias que tras realizar determinada una acción devuelven un resultado. En C la mayoría de las expresiones devuelven un entero. Hay tres tipos de expresiones:

Valores por la izquierda o lvalue (del ingles left value). Al evaluarlas devuelven la dirección de un cierto dato, que nos permitir acceder a ‚l para inicializarlo, modificarlo, etc. Se llaman así porque son las expresiones que normalmente se colocan en el lado izquierdo del = en una expresión de asignación. Suelen ser nombres de variables, elementos de un arreglo, etc.

33

Page 34: Programacion en Java

Valores por la derecha o rvalue (del ingles right value). Al evaluarlas obtenemos un dato de cierto tipo. Normalmente son las expresiones que se colocan a la derecha del = en la asignación. Ejemplos de ellos son los contenidos de las variables, los datos contenidos en los arreglos y los valores constantes.

Llamada a una función. Si la función no es de tipo void, al llamarla devolver un dato. Este dato puede ser usado como una expresión del tipo valor por la derecha.

Tipos de expresiones

El primer tipo a considerar dada su importancia es la asignación. La sintaxis de una asignación es

lvalue = rvalue;

Para evaluar una asignación el compilador evalúa el lado derecho. El dato que obtiene es entonces cargado en la dirección que resulta de evaluar el lado izquierdo. Por ejemplo:

i = 1;

introduce el entero 1 en la dirección de la variable i. Como toda expresión la asignación devuelve un valor, que es el mismo que se carga en la variable. Esto puede ser usado para inicializar varias variables con el mismo valor. Por ejemplo, si tenemos tres variables enteras i, j y k:

i = j = k = 0;

las inicializa a las tres a 0. Observar que una misma variable puede estar en ambos lados de una asignación:

i = i + 1;

incrementa la variable i en una unidad.

El segundo tipo de expresiones son los operadores. Hay varios tipos de operadores:

-operadores aritméticos: Son el +, - , *, % y /, que realizan las operaciones aritméticas básicas. Estas expresiones tienen de sintaxis:

rvalue operador rvalue

Los operadores aritméticos son:

+ el operador de suma

- el operador de resta

* el la multiplicación

/ la división entera

% el resto de la división (operador modulo).

Por ejemplo:

34

Page 35: Programacion en Java

i = a + 1;

j = b * 4 + 10 / 2;

Operadores de incremento y decremento. Sirven para incrementar una cierta variable. Admiten cuatro posibles combinaciones:

++lvalue incrementa el contenido de lvalue y devuelve el contenido nuevo

--lvalue decrementa el contenido de lvalue y devuelve el contenido nuevo

lvalue++ incrementa el contenido de lvalue y devuelve el valor que contenía antes de incrementarlo.

lvalue-- decrementa el contenido de lvalue y devuelve el valor que contenía antes de decrementarlo.

Por ejemplo:

i = j++; /* carga en i el valor de j y */

/* luego incrementa j */

i = ++j; /* incrementa j y luego carga su valor en i */

Estos operadores son muy usados en las variables de los bucles y con los punteros. El tipo de datos que devuelven es el del lvalue.

Operadores de comparación. Son:

< "menor que"

> "mayor que"

<= "menor o igual que"

>= "mayor o igual que"

== "igual que"

!= "no igual que"

La sintaxis para estas expresiones son:

rvalue operador rvalue

El valor que devuelve es de tipo entero: devuelve un 0 si el resultado de la comparación es falso y un valor distinto de 0 si el resultado de la comparación es verdadero. En C el cero se toma como valor falso, y cualquier valor diferente del cero es verdadero. El valor concreto empleado para

35

Page 36: Programacion en Java

representar el valor verdadero es irrelevante, y normalmente depende del sistema empleado. Cuando lo que comparamos son caracteres, se compara realmente su código ASCII. Por ejemplo:

1 > 2 devuelve un valor verdadero

1 == 1 devuelve un valor verdadero

'1' == 1 devuelve falso

'a' < 'b' es verdadero

Operadores lógicos

Realizan las operaciones lógicas habituales en el álgebra de Bool. Realizan el AND (Y lógico), el OR (O lógico) y el NOT (negación lógica). Son:

&& AND (Y lógico)

|| OR (O lógico)

! NOT (negación lógica

Los dos primeros son operadores binarios y el tercero es un operador unario. Su sintaxis es:

expresion1 && expresion2

expresion1 || expresion2

!expresion1

El resultado de evaluar la expresión AND es verdadero si ambos son verdaderos, y falso en caso contrario. El resultado de evaluar la expresión OR es verdadero si alguna o las dos expresiones es verdadera. Es falsa si las dos expresiones son falsas. El resultado de la expresión NOT es falso si la expresión es verdadera, y verdadero si la expresión es verdadera.

Para evaluar estos operadores se toma como verdadero un valor de la expresión distinto de 0 y como falso un valor 0.

Control del flujo del programa

En C las sentencias se ejecutan sucesivamente una tras otra. Esto define un camino o dirección según la cual se va desarrollado el programa. Sin embargo, habrá momentos en que el programa deba ejecutar determinadas partes dependiendo del estado en el que se halle el programa o de las variables externas. Esto permitir modificar el orden de la ejecución para adaptarse al estado del programa y bifurcar hacia nuevas subrutinas cuando se cumplan ciertas condiciones, que el programador fijar de antemano.

La sentencia if

La primera sentencia de control es la sentencia if. Admite dos tipos de sintaxis:

if (expresión1)

36

Page 37: Programacion en Java

sentencia1;

o también:

if (expresión1)

sentencia1;

else

sentencia2;

Esta sentencia es equivalente a la que poseen la mayoría de lenguajes de programación y sirve para bifurcar en un punto de programa. la sentencia if permite tomar decisiones al programa. En su primera forma la sentencia1 sólo se ejecuta si el resultado de evaluar la expresión1 es verdadero (distinto de cero). En la segunda forma tenemos dos posibilidades: si al evaluar la expresión1 el resultado es verdadero se ejecuta la sentencia1, pero si el resultado es falso se ejecuta la sentencia2. En cualquier caso sólo una de las dos sentencias se ejecuta. Por ejemplo:

if (numero1 == 1)

puts("la variable numero1 vale 1");

else

puts("la variable numero1 no vale 1");

Tras evaluarse la expresión if y ejecutarse la sentencia adecuada, el programa continua con la línea siguiente a la de la ultima sentencia del if. Para la sentencia if vale como expresión cualquier expresión v lida en C, incluso las asignaciones y llamadas a funciones. El caso en que la expresión es una asignación suele ser sorprendente, ya que en la mayoría de los lenguajes este tipo de expresiones no es valido. Como sentencia vale cualquier tipo de sentencia v lida en C, entre ellas la propia sentencia if. En este caso hablaremos de sentencias if anidadas. Por ejemplo:

if (num > 0)

if (num == 1)

puts("num es igual a 1")

else

puts("num es mayor que 1)

else

puts("num es menor que 1");

Cuando hay dos if anidados y a continuación hay un else, este else pertenece al ultimo if. Así en el caso anterior el primer else corresponde al segundo if. Si queremos que un else pertenezca al primer if de un if anidado deberemos encerrar al segundo entre paréntesis. Por ejemplo:

37

Page 38: Programacion en Java

if (num > 0)

{

if (num == 1)

puts("num es igual a 1");

}

else

puts("num es menor que 0");

Cuando necesitamos ejecutar varias sentencias que depende de un if, utilizaremos la sentencia de tipo bloque de sentencias. Un bloque de sentencias es un grupo de sentencias encerradas entre llaves { y }. Por ejemplo:

if (num >= 0) {

printf("num %d\n");

if (num == 0)

puts("num 0");

if (num >= 1)

puts("num mayor o igual a 1");

}

El bucle while

Un bucle es un conjunto de sentencias que se ejecutan repetidamente hasta que se alcanza una condición de fin de bucle o condición de salida. El bucle while es el tipo de bucle más sencillo. En su modo más simple se escribe:

while (expresión1)

sentencia1;

El bucle while comienza por evaluar la expresión1. Si es cierta se ejecuta la sentencia1. Entonces se vuelve a evaluar la expresión1. De nuevo si es verdadera se vuelve a ejecutar la sentencia1. Este proceso continua hasta que el resultado de evaluar la expresión es falso. Por esto se le llama a esta expresión la condición de salida. Por ejemplo:

int variable = 10;

while (variable)

printf("la variable vale %d\n", variable--);

38

Page 39: Programacion en Java

En este caso se imprimir el valor de la variable hasta que se llegue a 1. Normalmente en las sentencias del bucle while se coloca alguna instrucción que modifique la expresión de control. Lo más habitual es utilizar un bloque de sentencias en vez de una sentencia en vez de una sentencia única. Por ejemplo:

int variable = 10;

while (variable) {

printf("valor de la variable %d\n", variable);

printf("valor tras decrementar la variable %d\n", variable);

}

El bucle do-while

La sintaxis de este bucle es:

do

sentencia1;

while (expresión1);

Su funcionamiento es análogo el del bucle while, salvo que la expresión de control se evalúa al final del bucle. Esto nos garantiza que el bucle do-while se ejecuta al menos una vez. Es menos habitual que el bucle while.

Podemos incluir dentro del bucle un grupo de sentencias, en vez de la sentencia1. Este es el único bucle que no necesita llaves para encerrar un grupo de sentencias. Por ejemplo:

char c = '9';

do

printf("numero actual %c\n", c);

--c; /* ahora la decrementamos como si fuera entera */

while (c >= '0');

El bucle for

La sintaxis del bucle for es:

for (inicio, control, incremento)

sentencia1;

39

Page 40: Programacion en Java

Este bucle se utiliza para realizar una acción un número determinado de veces. Está compuesto de tres expresiones: la de inicio, la de control y la de incremento, y una sentencia. Su versión más sencilla es:

for (i =0; i < 10; i++)

printf("i vale %d\n", i);

Esta versión del bucle imprime un mensaje en la pantalla mientras que no se alcance la condición de salida, i == 10.

El funcionamiento del bucle for es el siguiente:

Primero se ejecuta la expresión de inicio. Normalmente ‚esta es una expresión de asignación a una variable, que le da un valor inicial.

Luego se comprueba la expresión de control. Si esta expresión es verdadera se ejecuta la sentencia, o el grupo de sentencias. Si la expresión es falsa el bucle finaliza.

Tras ejecutarse la sentencia se evalúa la expresión de incremento.

Habitualmente lo que hace esta expresión es incrementar la variable de control.

A continuación se vuelve al segundo paso. El bucle finaliza cuando la expresión de control es falsa.

En un bucle for podemos omitir la expresión de inicio, por ejemplo si sabemos que la variable ya esta inicializada:

int i = 0;

for ( ; i <= 10; ++i)

printf("%d\n", i);

También podemos omitir la expresión de incremento. Esto es habitual cuando la variable de control ya se modifica dentro del bucle. Por ejemplo:

int i= 10;

for ( ; i < 10; )

printf("i = %d\n", i++);

El bucle for es equivalente a un bucle while escrito del siguiente modo:

inicio;

while (control) {

sentencia1;

40

Page 41: Programacion en Java

incremento;

}

Este bucle while puede servirnos para salir fácilmente de dudas al escribir un bucle for, ya que se ve claramente el orden de ejecución de las expresiones y sentencias dentro del bucle for. Como se ve, en cada pasada del bucle for se sigue el orden: evaluación de control, ejecución de sentencia1 y evaluación de incremento.

Las sentencias break y continue

Hay veces en que interesa romper un bucle en una determinada posición, para ejecutar una nueva pasada del bucle o para finalizar su ejecución Esto suele ser habitual cuando el bucle tiene una gran complicación o cuando necesitamos salir "por las malas" de ‚l. Esto ultimo suele ser frecuente cuando en el bucle se producen "condiciones de error". Para realizar estos dos tipos de salto disponemos de dos sentencias, la sentencia break y la sentencia continue.

La sentencia break rompe la ejecución de un bucle o bloque de instrucciones y continua en la instrucción que siga al bucle o bloque. Por ejemplo:

int a = 10;

while (1) {

if (a-- <= 1)

break;

printf("%d\n", a);

}

Aunque en apariencia ‚este es un bucle sin fin, ya que la condición con while (1) siempre es cierta, este bucle se acabar cuando la variable a valga 1. El bucle simplemente decrementa la variable e imprime su valor.

La sentencia continue rompe la ejecución habitual del bucle y procede a evaluar de nuevo la expresión del bucle. Actúa como si se saltase al final del bloque de un bucle. Por ejemplo:

int a = 1;

while (a < 10) {

printf("%d\n", a);

if (a==7)

continue;

}

La sentencia de selección múltiple switch

41

Page 42: Programacion en Java

Esta sentencia sirve para agrupar varias sentencias if en una sola, en el caso particular en el que una variable es comparada a diferentes valores, todos ellos constantes, y que realiza acciones si coincide con ellos. Su sintaxis es:

switch (control) {

case exp1: sent1; break;

case exp2: sent2; break;

default sent0; break;

}

Su sintaxis es más complicada que la de anteriores bucles, ya que agrupa un mayor número de acciones y posibilidades en una sola sentencia. El modo de funcionamiento es el siguiente:

Primero se evalúa la expresión de control.

A continuación se compara con la expresión de la primera etiqueta case. Si son iguales se ejecuta la sentencia1.

Luego se vuelve a comparar la expresión de control con la etiqueta del segundo case. De nuevo , si son iguales se ejecuta la sentencia2.

Se repite el proceso hasta agotar todas las etiquetas case. Si al llegar a la etiqueta default no se ha ejecutado ninguna otra sentencia. Esta es la acción por defecto. La etiqueta default es opcional. Si no la ponemos el programa simplemente salta a la línea siguiente.

Hay que tener cuidado con un aspecto de este bucle. Cuando una expresión de una etiqueta case es igual a la sentencia de control, se ejecutan todas las sentencias que sigan hasta que se alcance una nueva etiqueta case, y luego se vuelve a comparar la expresión de control. Este mecanismo tiene una ventaja y un inconveniente. La ventaja es que no necesitamos encerrar entre llaves el grupo de sentencias a ejecutar para cada etiqueta. El inconveniente es que al agotar las sentencias de una determinada etiqueta la sentencia switch prosigue con la siguiente etiqueta case. Habitualmente lo que se pretende es que tras ejecutar el grupo de sentencias se finalice el switch. Para evitar el que se ejecuten más sentencias habitualmente se acaba cada grupo de sentencias con una sentencia break. La sentencia break pasa entonces la ejecución a la siguiente línea de programa que prosiga al bucle switch.

También se permite poner etiquetas múltiples para un mismo grupo de sentencias. Si dejamos una etiqueta case sin sentencias a ejecutar entonces se asocia a la siguiente etiqueta. Esto es útil para ejecutar una misma acción para distintos valores de la expresión.

Funciones

Una función es una rutina o conjunto de sentencias que realiza una determinada labor. En C todas las funciones devuelven un valor, que por defecto es un entero. las funciones admiten argumentos, que son datos que le pasan a la función las sentencias que la llaman.

Definición de una función

La sintaxis habitual en la definición de una función es:

42

Page 43: Programacion en Java

tipo identificador(lista_de_argumentos)

{

/* bloque de código */

}

Donde:

tipo es el tipo de datos devuelto por la función

identificador es el nombre de la función. Debe ser un identificador valido.

lista_de_argumentos es una lista de variables, separadas por comas, que conforman los datos que le pasamos a la función.

El tipo y la lista de argumentos son opcionales. Si omitimos el tipo, la función por defecto devolver un entero. Muchas veces el valor devuelto por la función es ignorado en el programa.

La lista de argumentos es también opcional. Un ejemplo es la función main(), que en principio no tiene argumentos. Podemos escribir como ejemplo:

hola()

{

printf("hola\n");

}

Que simplemente es una función que cuando es llamada imprime en pantalla un mensaje de saludo.

Cuando el programa al ejecutarse alcanza la llave de cierre '}' de la función, esta finaliza y devuelve el control al punto del programa que la llamó.

Retorno de valores

Cuando la función finaliza hemos dicho que se devuelve un valor. Este valor en principio no est definido, es decir, puede devolver cualquier cosa.

Para obligar a la función a retornar un determinado valor se utiliza la sentencia return, seguida del valor a retornar. Como todas las sentencias en C se debe acabar con un ';'. Por ejemplo:

lista()

{

return 1;

43

Page 44: Programacion en Java

}

devuelve el entero 1 cada vez que es llamada. En C podemos devolver cualquier tipo de datos de los llamados escalares. Los tipos de datos escalares son los punteros, tipos numéricos y el tipo carácter. En C no se pueden devolver arreglos ni estructuras.

Paso de parámetros a una función

Utilizando la lista de argumentos podemos pasar par metros a una función.

En la lista de par metros se suele colocar un conjunto de identificadores, separados por comas, que representar cada uno de ellos a uno de los par metros de la función. Observar que el orden de los par metros es importante. Para llamar a la función habrá que colocar los par metros en el orden en que la función los espera.

Cada par metro puede tener un tipo diferente. Para declarar el tipo de los par metros añadiremos entre el paréntesis ')' y la llave '{' una lista de declaraciones, similar a una lista de declaraciones de variables. Es habitual colocar cada par metro en una línea, tabulados hacia la derecha.

Asi:

imprime(numero, letra)

int numero;

char letra;

{

printf("%d, %c\n", numero, letra);

}

es una función que admite dos variables, una entera u otra de tipo carácter.

Paso de par metros por valor y por referencia

En los lenguajes de programación estructurada hay dos formas de pasar variables a una función: por referencia o por valor. Cuando la variable se pasa por referencia función puede acceder a la variable original. Este enfoque es habitual en lenguajes como el Pascal.

En C sin embargo todos los par metros se pasan por valor. La función recibe una copia de los par metros y variables, y no puede acceder a las variables originales. Cualquier modificación que efectuemos sobre un par metro no se reflejar en la variable original. Esto hace que no podamos alterar el valor de la variable por equivocación.

Sin embargo, en determinadas ocasiones necesitaremos alterar el valor de la variable que le pasamos a una función. Para ello en el C se emplea el mecanismo de los punteros, que se ve mas adelante.

Declaración y comprobación de tipos

44

Page 45: Programacion en Java

Al igual que para las variables, cuando una función se va a usar en un programa antes del lugar donde se define, o cuando una función s define en otro fichero (funciones externas), la función se debe declarar.

La declaración de una función consiste en especificar el tipo de datos que va a retornar la función. Esto es obligatorio cuando vamos a usar una función que no devuelve un entero. Además en la declaración se puede especificar el número de argumentos y su tipo. Una declaración típica de función es:

tipo identificador( lista_de_argumentos_con_tipo );

Esto avisa al compilador de que la función ya existe, o que la vamos a definir después.

La lista de argumentos con tipo difiere de la lista de argumentos antes presentada en que el tipo de cada argumento se coloca dentro de la lista, antes de su correspondiente identificador, como hacíamos en la definición de variables. Por ejemplo:

char print(int numero, int letra);

declara una función que devuelve un carácter y tiene dos par metros, un entero y un carácter.

La lista de argumentos permite al compilador hacer comprobación de tipos, ya que el tipo y numero de argumentos debe coincidir en la declaración, definición y llamada a una función.

Este tipo de especificación del tipo de argumentos también se puede emplear en la definición de las funciones, aunque lo contrario no es posible. Asi:

char print(int numero, int letra)

{

printf("%d, %c\c", numero, letra);

}

es otra definición v lida para la función print que hemos empleado.

Arreglo

Los arreglos son conjuntos de datos de un mismo tipo, el tipo base. A cada uno de los datos de un arreglo le llamaremos elemento de arreglo, y esta designado por un número. En C al primer elemento de un arreglo le corresponde siempre el número 0, y los demás tienen la numeración consecutiva.

Declaración de una matriz.

Para crear un arreglo de n elementos de un cierto tipo se introduce la línea:

Tipo identificador [n];

45

Page 46: Programacion en Java

donde n es una constante de tamaño fijo. Si el arreglo es estático o global el compilador crea el espacio para la matriz al principio del programa, generalmente en el rea de datos. Si es de tipo automático, reservar el espacio en la pila de datos.

Como todos los tipos de datos , un arreglo se puede inicializar. Si el arreglo es estático, por defecto cada elemento se inicializa a 0. Si es din mico los valores de cada elemento no est n definidos y antes de usarlos los debemos inicializar. Para inicializar un arreglo en el momento de su creación añadiremos tras el identificador y los corchetes de tamaño un = y la serie de valores. Cada valor debe ser una constante v lida para el tipo de datos del arreglo, y cada valor ir separado del valor precedente mediante una coma. Para abrir y cerrar la serie de valores usaremos las llaves. Por ejemplo:

int vector [4]={0, 1,2,3 };

char hola[] = { 'h', 'o', 'l', 'a', '\0'};

No podemos dar un número de valores mayor al tamaño del arreglo , pero si podemos dar menos de los necesarios. El compilador siempre rellenar los demás con ceros. El compilador siempre asignar el primer valor al primer elemento del arreglo, y los demás los asignar consecutivamente. Como siempre acabaremos la línea con un ;.

Acceso a los miembros de un arreglo

Para usar un elemento de un arreglo se utiliza el identificador y el número de orden del elemento. Al primer elemento siempre le corresponde el número 0. Asi

printf ("%d", vector[0])

imprimiría el contenido del primer elemento del arreglo que definimos antes, que lo habíamos inicializado a 0.

En el lenguaje C no se hace ningún control acerca de si intentamos leer un número de elemento mayor que el ultimo número del arreglo. Esto es lo que llama sobrepasar el límite, y el compilador deja al programador la tarea de preocuparse por los límites del arreglo. Si los sobrepasamos, pueden ocurrir resultados imprevisibles (normalmente modificaremos alguna otra variable del programa, o incluso bloquearemos el programa).

Tamaño de los arreglos

El tamaño de los arreglos es siempre constante y se especifica al crear el arreglo. Hay dos formas de especificar el tipo: índice dándoselo explícitamente al compilador o haciéndolo implícitamente. El primer modo es el ya señalado anteriormente . Para dar un tamaño al arreglo simplemente indicamos el número de elementos entre los corchetes. Este es el modo más habitual de dar el tamaño, sobre todo si no se va a inicializar en el momento de su creación. El otro modo consiste en hacer que sea el compilador el que decida el tamaño. Esto se hace cuando en la creación del arreglo le damos una lista de valores iniciales . En este caso si omitimos el tamaño del arreglo el compilador ajusta el tamaño del arreglo según el número de elementos que le demos para inicializar el arreglo. Por ejemplo:

int vetor[] = { 1, 2, 3, 4, 5, 6 };

Este ultimo m‚todo es muy cómodo, sobre todo si el arreglo va a tener un tamaño pequeño y todos los valores iniciales son constantes.

46

Page 47: Programacion en Java

Cadenas de caracteres

Hay un tipo de arreglos de especial importancia; las cadenas de caracteres.

Una cadena de caracteres es un arreglo de caracteres que acaba con el carácter nulo. En C siempre las cadenas de caracteres acaban con este carácter. Esto se hace así por dos motivos: el tamaño de la cadena no tiene un límite prefijado: puede ser tan grande como lo permita la memoria. Las operaciones de manipulación de cadenas de caracteres se simplifican bastante. El inconveniente es que para conocer el tamaño de la cadena normalmente necesitamos recorrerla con un bucle, aunque esto suele hacerse rápidamente.

Para inicializar una cadena de caracteres basta crear un arreglo de caracteres, en el que no necesitamos definir el tamaño e inicializarlo con la cadena de caracteres entrecomillada. Observar que el compilador siempre añade un carácter nulo al final, por lo que el tamaño del arreglo es una unidad mayor del aparente. Por ejemplo:

char cadena[] = "abracadabra" /* cadena de 12 caracteres*/

Los caracteres especiales como el tabulador \t y el retorno de carro \r se almacenan como un único carácter. El carácter nulo est representado por un 0. Esto nos permitir utilizar comparaciones con este carácter en los bucles que recorren cadenas de caracteres .

Arreglos multidimensionales

En C se pueden construir arreglos de arreglos , es decir tipos de arreglos tipos de arreglos cuyos elementos son a su vez arreglos. Dado que ahora necesitaremos un índice para situarnos dentro del arreglo principal y otro más para movernos dentro de cada uno de los nuevos arreglos , diremos que los arreglos de arreglos poseen dos dimensiones. A un arreglo de dos dimensiones se le suele llamar matriz, y a un arreglo de una dimensión, vector. Las matrices son tipos de datos ampliamente usados en matemáticas. Normalmente diremos que un índice representa a las filas de la matriz y otro a las columnas. Otro uso habitual de las matrices es para representar tablas de valores.

Para crear una matriz de enteros, es decir, un arreglo de arreglos de enteros, lo haremos de modo análogo a cuando creábamos un arreglo, salvo que ahora añadiremos el nuevo índice entre corchetes. Por ejemplo:

int matriz[8][9];

declara una matriz de 8 filas por 9 columnas, o 9 por 8 columnas, según queramos representar. La elección de cual ííndice representa las filas y cual las columnas es arbitrario. Podemos usar la norma habitual en matemáticas: el de la izquierda representa filas y el de la derecha columnas.

Acceso a los miembros de una matriz

Para acceder a un miembro concreto de una matriz, siguiendo el convenio anterior, colocaremos su número de fila y de columna entre corchetes. Por ejemplo:

printf("%d\n", matriz[1][2]);

imprime el elemento correspondiente a la fila 1, columna 2.

Para inicializar las matrices disponemos ahora de dos formas:

47

Page 48: Programacion en Java

-incluir todos los elementos de la matriz entre llaves, ordenados por filas y por columnas. Para ello se hace del siguiente modo: tras el signo igual en la definición abrimos una llave. A continuación colocamos cada columna de valores, todos ellos separados por sus comas. Cada columnas de valores debe ir encerrada entre llaves, separando una columna de otra por comas. Al final cerramos la llave que habíamos abierto al principio. Por ejemplo:

int matriz[2][3] = { {1, 2, 3}, {4, 5, 6} };

define una matriz de 2 filas con 3 columnas por fila.

-incluir la lista completa de elementos de la matriz entre llaves, separados por comas, uno tras otro sin separar las columnas. Para colocar todos los valores correctamente necesitamos saber como accede el compilador a los elementos de una matriz. El compilador supone que en la memoria est n guardados los elementos ordenados por filas, es decir, est n juntos todos los datos correspondientes a una fila. Así la matriz la podíamos haber inicializado con:

int matriz[2][3] = { 1, 2, 3, 4, 5, 6};

y obtendremos el mismo resultado de antes. Vemos que los tres primeros elementos corresponden a la fila 1, y los tres siguientes son los de la fila 2. Cuando el compilador necesita acceder al elemento de fila i y columna j hace el siguiente calculo: multiplica (i - 1) por el número de columnas, y al resultado le suma (j - 1). Con ello accede a la matriz como si fuese de una sola dimensión. En nuestro caso el elemento matriz[1][3] ser el que ocupe el lugar 0 * 3 + 2, es decir, el tercer elemento de un arreglo unidimensional. Los dos unos que aparecen restando se deben a que el compilador empieza a contar los elementos de la matriz desde el 0.

Como cabe esperar se pueden formar arreglos de cualquier dimensión. Por ejemplo un arreglo de tres dimensiones podría ser:

tresdim[10][15][20];

Esto podría representar un conjunto de 10 tablas, cada una de 15 filas y 20 columnas por fila. Para inicializarla procederíamos de un modo análogo al caso bidimensional.

Punteros

Cuando queramos pasar un dato a una función normalmente pasamos una copia del dato. Esto es sencillo de hacer y r pido, siempre que no queramos modificar mediante la función el dato original o que el dato sea pequeño.

Otro enfoque consiste en decirle a la función donde encontrar los datos. Para ello le pasamos a la función una dirección. Con ella la función podré acceder a los datos utilizando un puntero. Un puntero es un nuevo tipo de datos, que no contiene un dato en si, si no que contiene la dirección donde podemos encontrar el dato. Decimos que un puntero "apunta" a un dato, pudiendo alterar dicho dato a través del puntero.

Definición de un puntero

Para poder usar punteros y direcciones de datos vamos a introducir dos nuevos operadores. el primero es el operador puntero, que se representa con un asterisco *. el operador puntero nos permite definir las variables como punteros y también acceder a los datos. El otro nuevo operador, el operador dirección, nos permite obtener la dirección en la que se halla ubicada una variable en la memoria. Vemos que el operador dirección es el complementario al operador puntero.

48

Page 49: Programacion en Java

Para definir un puntero lo primero que hay que tener en cuenta es que todo puntero tiene asociado un tipo de datos. Un puntero se define igual que una variable normal, salvo que delante del identificador colocaremos un asterisco. Por ejemplo:

char *pc; /*puntero a carácter */

char *pi; /* puntero a entero */

Normalmente al definir un puntero lo solemos inicializar para que apunte a algún dato. Disponemos de tres formas de inicializar un puntero:

Inicializarlo con la dirección de una variable que ya existe en memoria.

Para obtener la dirección en la que está ubicada una variable colocamos delante del identificador de la variable el operador dirección &. No se suele dejar espacios entre el signo & y el identificador. Por ejemplo:

char *p = &p1;

Asignarle el contenido de otro puntero que ya está‚ inicializado:

char *p = &p1;

char *p2 = p; /* p ya est inicializado */

Inicializarlo con cualquier expresión constante que devuelva un lvalue.

Las mas frecuentes son una cadena de caracteres, el identificador de un arreglo, el identificador de una función, y otros muchos. Este es un detalle importante: los identificadores de funciones y de arreglos son en si mismos valores por la izquierda (lvalues), por lo que se pueden usar directamente para inicializar punteros.

Una forma adicional de inicializarlo es darle directamente una posición de memoria. Este m‚todo no es portable, ya que depende del sistema, pero suele ser muy útil en programación de sistemas, que es uno de los usos fundamentales del C.

Un error muy frecuente consiste en no inicializar el puntero antes de usarlo. Este error frecuentemente lo localiza el compilador y avisa de ello.

Desreferenciacion de un puntero

Una vez que el puntero apunta a un objeto o dato en la memoria podemos emplear el puntero para acceder al dato. A este proceso se la llama desreferenciar el puntero, debido a que es una operación inversa a obtener la dirección de una variable. Para desreferenciar un puntero se utiliza el operador puntero. Para acceder al dato al que apunta el puntero basta colocar el asterisco * delante del identificador. Como norma de buena escritura no se deja ningún espacio entre el * y el identificador, aunque el compilador lo acepte. Un puntero desreferenciado se comporta como una variable normal. Por ejemplo:

int entero = 4, *p = &entero;

printf("%d %d \n", *p, entero);

49

Page 50: Programacion en Java

Aritmética de punteros. Un uso habitual de los punteros es para recorrer los arreglos. En efecto, comencemos por crear un arreglo y un puntero al comienzo del arreglo.

int arreglo[] ={ 1, 2, 3, 4, 5};

int *p = arreglo;

En este momento el puntero apunta al primer miembro del arreglo. Podemos modificar fácilmente el primer miembro, por ejemplo:

*p = 5;

printf("%d\n", arreglo[0];

Ya que un puntero es una variable también, le podemos sumar una cantidad.

Sin embargo el resultado no se parece al que obtenemos con variables. Si a un puntero de tipo carácter le sumamos 1 o lo incrementamos, el contenido de la variable puntero es aumentado una unidad, con lo que el puntero a caracteres apuntaría al siguiente miembro del arreglo. En principio este es el efecto que necesitaremos.

Supongamos que tenemos ahora nuestro puntero a enteros, y apunta al principio del arreglo. Si nuestro sistema necesita dos bytes para representar los enteros, tras incrementar un puntero en una unidad veremos que el contenido de la variable puntero ha aumentado en dos unidades. Esto lo podemos ver utilizando la función printf con el modificador %p. En efecto:

printf("%p\n", p); /* usamos el puntero anterior */

++p;

printf("%p\n", p;

El resultado es que el puntero apunta ahora al siguiente elemento del arreglo. Este modo de incrementar el puntero es muy conveniente pues nos permite recorre un arreglo fácilmente. Para recorrer el arreglo sólo tendremos que crear un puntero apuntando al principio del arreglo e irlo incrementando mientras manipulamos los datos del arreglo. Por ejemplo, el siguiente bucle imprime todos los caracteres de una cadena: MP!

char *p = "Hola, mundo.\n"; /* la cadena es un lvalue */

while (*p)

putchar(*p++);

Aquí observamos como se usa el operador incremento con los punteros. Ya que el operador puntero tiene mayor precedencia que el operador incremento, en la expresión *p++ primero se desreferencia el puntero, usándose en con la función putchar(), y luego se incrementa el puntero. Por eso no hacen falta los paréntesis con este operador. Si quisiésemos incrementar el carácter al que apunta el puntero, debemos encerrar entre paréntesis al operador puntero y al puntero. Por ejemplo:

50

Page 51: Programacion en Java

char *p = "Hola, mundo.\n"; /* la cadena es un lvalue */

++(*p); /* Aparecerá una I */

while (*p)

putchar(*p++);

Cadenas de caracteres

La manipulación de cadenas de caracteres está implementado en la librería estándar del C. Como habíamos definido, una cadena de caracteres es un arreglo de caracteres cuyo ultimo carácter es el carácter nulo '\0'. Para definir una cadena de caracteres basta definir un arreglo de caracteres del tamaño conveniente, dejando espacio para el carácter nulo. Por ejemplo:

char cadena[100] = "Hola";

La mayoría de las funciones de cadenas de la librería estándar comienzan con el prefijo str y se hayan definidas en el fichero de cabecera <string.h>. Las funciones mas importantes de esta librería son:

size_t strlen( const char *s);

La función strlen() devuelve el tamaño de una cadena de caracteres, sin incluir el carácter nulo de terminación. Por ejemplo:

printf("numero de caracteres de la palabra hola = %d", strlen("hola");

Necesita un par metro de tipo puntero a carácter y devuelve un size_t, que suele est r definido como entero sin signo.

char *strcpy(char *s1, const char *s2);

La función strcpy() copia la cadena s2 en la cadena s1, incluyendo el carácter de terminación y devuelve un puntero a s1. Los dos parametros que necesita son punteros a caracteres, y devuelve un puntero a caracteres.

Deberemos asegurarnos de que s1 tiene sitio para almacenar la cadena s2.

char *strcat(char *s1, const char *s2);

La función strcat() copia la cadena s2 al final de la cadena s1. Para ello busca el carácter de terminación de s1 y a partir de allí va colocando sucesivamente los caracteres de s2, incluyendo el carácter de terminación.

Los par metros que necesita y que devuelve son del mismo tipo que los de strcpy. Tampoco hace comprobaciones de que exista espacio para la cadena s2, ya que de ello debe asegurarse el programador.

char *strchr(const char *s, int c);

51

Page 52: Programacion en Java

La función strchr() busca el carácter c a lo largo de la cadena s. si lo encuentra devuelve un puntero a la primera posición del carácter. Si falla la búsqueda devuelve un puntero nulo. La función tiene dos par metros, el puntero a la cadena en la que buscar el carácter y el carácter a buscar.

Devuelve un puntero a caracteres.

int strcmp(const char *s1, const char *s2);

La función strcmp() compara dos cadenas de caracteres. Para ello compara elementos sucesivos de ambas cadenas hasta que encuentra dos elementos diferentes. Si ambas cadenas son iguales la función devuelve un 0. Si el elemento diferente es menor en s1 entonces devuelve un número negativo, y si es mayor en s1 entonces devuelve un número positivo. para comparar los caracteres la función toma los caracteres como enteros sin signo. Al utilizar el código ASCII para representar las cadenas, los números tienen un código menor que las minúsculas, y ‚stas que las mayúsculas. Esta función no es muy eficiente cuando se trata de ordenar cadenas en las que aparecen caracteres acentuados o eñes, pero es fácil construir una a medida.

char *strncat(char*s1, const char *s2, size_t n);

La función strncat() sirve para copiar un fragmento de la cadena s2 en al final de la cadena s1. Necesita tres par metros, dos punteros a caracteres y un número del tipo size_t, generalmente un unsigned. La función copia los primeros n caracteres de s2 al final de s1 y luego añade un carácter nulo al final de s1. La función devuelve un puntero a carácter que apunta a s1.

Estructuras

Una estructura es un tipo de datos compuesto por un grupo de datos, cada uno de los cuales puede ser de un tipo distinto. A cada componente de la estructura se le llama campo. Las estructuras tiene su equivalente en otros lenguajes de programación, como el Pascal, en los registros. También se llaman registros a los grupos de datos en la terminología de las bases de datos.

Definición de una estructura

Para la definición de estructuras el C dispone de la palabra reservada estruct. Para crear una estructura primero comenzamos por definir el tipo de estructura. Para ello se procede de manera parecida a la definición de una variable, con algunas modificaciones. Primero colocamos la palabra reservada struct y luego el identificador que dar nombre al nuevo tipo de estructura. Luego abriremos llaves y comenzaremos a definir los campos de la estructura. Cada campo se define como una variable normal, es decir, dando su tipo y un identificador. Vale cualquier definición de tipo habitual, incluso punteros y estructuras. El identificador servir para designar a cada campo. Cada definición de campo acabar con un punto y coma, como siempre. Finalizaremos la definición de la estructura con una llave de cierre y un punto y coma. Por ejemplo:

struct fecha { /* para almacenar una fecha */

int dia;

char mes[14];

int anyo;

52

Page 53: Programacion en Java

};

double real;

double imaginario;

};

Una vez que hemos definido un tipo de estructura ya podemos definir variables estructuras de dicho tipo. Esto se hace de una forma análoga a la definición de variables normales, esto es, se pone la palabra reservada struct, el identificador del tipo de estructura y el identificador de la nueva estructura. Por ejemplo:

struct fecha fecha_de_hoy;

struct complejo x, y;

Una estructura también se puede inicializar. Para ello se dan los valores iniciales entre llaves, separados por comas, al igual que hacíamos son los arreglos. La novedad es que ahora cada dato puede tener un tipo diferente.

Por ejemplo:

struct fecha fecha_actual = { 12, "Enero", 1900};

struct complejo x = {1, 1};

Hay dos posibilidades más en la definición de estructuras. La primera es definir una variable estructura a la vez que se define el tipo de estructura. Para ello hasta dar los identificadores de las nuevas variables estructuras después de la llave de cierre. Por ejemplo:

struct complejo{

double x ;

double y;

} z1, z2;

Además podemos definir variables estructuras sin tipo específico. Para ello basta omitir el identificador del tipo de estructura en la definición de la estructura, dando sólo el identificador de la variable estructura. De este modo la nueva variable va asociada al tipo creado. Por ejemplo:

struct {

int dia;

char mes[14];

int anyo;

} mi_aniversario;

53

Page 54: Programacion en Java

Campos de bits

Hay un nuevo tipo de datos que solo se puede usar con estructuras: el campo de bits. Un campo de bits se comporta igual que un entero sin signo, sólo que al definir el campo de bits se define el número de bits que lo compondrá . Por Ejemplo:

struct comida {

unsigned clase : 2; /* dos bites para el tipo */

unsigned temporada : 1; /* a 1 si es de temporada */

unsigned es_perecedero :1, es_congelado : 1;

};

Si el tamaño del campo de bits es 0 nos permite alinear el siguiente campo sobre un entero. Hay que tener en cuenta que la alineación de los campos de bits y de los demás campos la define el compilador.

Hay que tener en cuenta que no se puede obtener la dirección de un campo de bits. El C estándar define la macro offsetof() para calcular el desplazamiento de un campo de una estructura desde el principio de la misma.

El uso de campos de bits permite empaquetar información pequeña eficientemente dentro de una estructura. Su uso est bastante extendido en la programación de sistemas.

Paso de estructuras a las funciones

En C estándar esta permitido pasar una estructura como argumento de una función, devolverla como resultado de una función, asi como asignar una estructura completa a una función. Esto no era posible en los primeros compiladores de C. No est permitido comparar estructuras completas. Por ejemplo:

struct complejo {

double x, y;

} z1, z2, suma;

struct complejo suma_complejos( struct complejo j1, struct complejo j2);

suma = suma_complejos(j1, j2); /*esta función se definiría aparte */

if (j1 > j2)

puts("es mayor j1"); /*esto no est permitido */

54

Page 55: Programacion en Java

Sin embargo esto no es lo más eficiente. Como las estructuras tienen a menudo un tamaño considerable suele ser conveniente pasarlas a través de punteros. Para obtener la dirección donde se halla una estructura usaremos el operador dirección &, como con una variable normal. Para definir un puntero a una estructura usaremos el operador *. Por ejemplo:

struct complejo j;

struct complejo *pj = &j;

Acceso a los campos de una estructura.

Para acceder individualmente a cada campo de una estructura se usa el operador punto '.'. Para ello colocamos el operador de la estructura , un punto y el identificador del campo. Cada campo de una estructura designado mediante este operador se comporta como si de una variable del mismo tipo que el campo se tratase. Podemos realizar todas las operaciones habituales de las variables: asignación, uso de punteros, llamadas a funciones con el campo como par metro:

struct complejo z = {1,1};

printf("z vale %f, i%f\n", z.x, z.y);

z.x = z.y = 0;

Para acceder a los campos de una estructura a través de un puntero tenemos un nuevo operador, el operador puntero a campo -> (un guión seguido de un signo "mayor que"). Para acceder a un campo de una estructura a través de un puntero a ella basta poner el identificador del puntero, el nuevo operador puntero a campo y luego el identificador del campo. Por ejemplo:

struct complejo z = {1, 1};

struct complejo *pz = &z;

printf("%f, i%f\n", pz->x, pz->y);

Podríamos haber usado la notación habitual para punteros y el operador punto de acceso a campos, pero nos aparece un nuevo inconveniente: ya que el operador punto tiene mayor precedencia que el operador puntero tendríamos que encerrar el identificador y el operador puntero entre paréntesis, para luego aplicarles el operador punto. En efecto, las siguientes expresiones son equivalentes:

pz->x = 1;

(*pz).x = 1;

Si omitimos los paréntesis se haría lo siguiente: pz sería considerado como una estructura, luego el operador punto daría el campo x de la estructura y el operador puntero intentaría acceder a donde apuntase el campo x. Vemos que hay dos errores, ya que ni pz es una estructura ni el campo x es un campo puntero. Esta expresión sería v lida si tuviésemos una estructura del tipo:

struct elemento {

int tipo;

55

Page 56: Programacion en Java

char *nombre;

} uno = {1, "estructura"};

printf("%c\n",*uno.nombre); /*imprime la primera letra de nombre */

Uniones

Una unión es un tipo de datos formado por un campo capaz de almacenar un solo dato pero de diferentes tipos. Dependiendo de las necesidades del programa el campo adoptar uno de los tipos admitidos para la unión. Para definir uniones el C utiliza la palabra reservada unión. La definición y el acceso al campo de la unión es análogo al de una estructura. Al definir una variable de tipo unión el compilador reserva espacio para el tipo que mayor espacio ocupe en la memoria. Siempre hay que tener en cuenta que sólo se puede tener almacenado un dato a la vez en la variable. En C es responsabilidad del programador el conocer que‚ tipo de dato se est guardando en cada momento en la unión.

Para definir una unión seguimos la misma sintaxis que para las estructuras.

Por ejemplo:

unión dato_num {

int num1;

float num2;

} dato;

define una unión en la que el campo puede ser de tipo entero o de tipo número con coma flotante.

Las uniones normalmente se emplean como campos en las estructuras. Para llevar la cuenta del tipo de datos almacenado en la unión normalmente se reserva un campo en la estructura. Por ejemplo:

struct dato_num {

int tipo;

unión {

float simple;

double doble;

}dato;

};

Las uniones son especialmente útiles para la realización de registros de bases de datos, ya que permiten almacenar información de diferentes tipos dentro de los registros. En programación de

56

Page 57: Programacion en Java

sistemas es usual encontrarlas dentro de las estructuras de datos de las rutinas, ya que permiten una gran flexibilidad a la hora de almacenar información.

Tipos de datos enumerados

Gestión de la memoria

En C se pueden almacenar variables y estructuras de datos en tres lugares diferentes: la pila para las variables automáticas, la memoria global para las variables globales y la memoria dinámica. La pila es una sección de la memoria que es gestionada automáticamente por el compilador y es donde se almacenan las variables locales. El compilador crea el espacio para la variable en tiempo de ejecución y libera el espacio ocupado cuando la variable deja de usarse (cuando salimos del ámbito o función en que se declaró). Por eso reciben el calificativo de automáticas.

Las variables estáticas globales se almacenan en la sección de la memoria llamada memoria global. El compilador también se encarga de proporcionarles espacio a estas variables, pero lo hace en tiempo de compilación, por lo que el espacio queda reservado durante toda la ejecución del programa.

Generalmente los valores iniciales de las variables globales son asignados en tiempo de compilación.

El ultimo tipo de memoria es el almacenamiento libre (free store en ingles) conocido habitualmente como la memoria dinámica (heap en ingles). El compilador no se hace cargo de ella, sino que es el programador el que solicita su uso a través de funciones predefinidas que se encargan de manejar la memoria dinámica. Estas funciones son malloc, calloc, realloc y free, y se definen en el fichero de cabecera <stdlib.h>. Estas funciones generalmente trabajan solicitando directamente porciones de memoria al sistema operativo. Su uso es muy sencillo y generalmente se usan a través de punteros.

La función malloc() nos permite solicitar memoria al sistema. Posee un único argumento: el tamaño del espacio que queremos reservar. En C el tamaño de un tipo de datos es el número de caracteres que ocupa en la memoria, ya que el tipo carácter tiene un tamaño de un byte generalmente.

El tamaño de un tipo de datos también se puede calcular con el operador sizeof. La función malloc se define como:

void * malloc(size_t longitud);

y su nombre viene de memory alloc (asignación de memoria, en ingles).

Necesita un par metro, que es la longitud del espacio que vamos a reserva. Este par metro es del tipo size_t, que es el tipo empleado en c para medir tamaños de tipos. Normalmente se suele definir size_t como unsigned, y el valor longitud representa el número de caracteres que se asignan. La función malloc devuelve un puntero del tipo puntero a caracteres al espacio de memoria asignado. Si el sistema no puede proporcionar la memoria pedida devuelve un puntero nulo. El espacio que devuelven las funciones malloc, c alloc y realloc no est inicializado, por lo que lo que debe inicializarlo el programador.

Para liberar el espacio asignado con malloc basta llamar a la función free, (liberar en ingles). Esta función se define como:

57

Page 58: Programacion en Java

void free(void *ptr);

y su argumento es el puntero que devolvió malloc. No devuelve ningún valor y funciona igualmente con los punteros que devuelven calloc y realloc.

La función calloc funciona de modo análogo a malloc salvo que tiene un par metro adicional, numelem, que le permite especificar el número de objetos a asignar. Se define como:

void *calloc(size_t numelem, size_t longitud);

Como la función malloc devuelve un puntero al espacio de memoria asignado y un puntero null si no ha sido posible asignar el espacio. Normalmente se utiliza para asignar espacio para un grupo de datos del mismo tipo.

La función realloc nos permite modificar el tamaño del espacio asignado con malloc o calloc. Se define como:

void *realloc(void *ptr, size_t longitud);

El primer argumento ptr es un puntero a un espacio previamente asignado con calloc o malloc. El segundo es la nueva longitud que le queremos dar.

Devuelve un puntero al nuevo espacio asignado o un puntero nulo si falló la asignación. Además la función copia el contenido del antiguo espacio en el nuevo al comienzo de este. Esto siempre lo puede hacer se la longitud del anterior espacio asignado es menor que la nueva longitud solicitada. El espacio sobrante en este caso no se inicializa. Si el espacio solicitado es de menor tamaño que el anterior se copia sólo la parte que quepa, siempre desde el principio, al comienzo del nuevo espacio asignado.

Uso de las funciones de asignación de memoria

Para reservar espacio en la memoria dinámica para un arreglo comenzaremos asignando el espacio con malloc. Por ejemplo:

#include <stdlib.h>

main()

{

char *p;

int i;

p = (char *) malloc(1000);

for (i = 0; i < 1000; ++i)

p[i] = getchar();

for (i = 999; i >= 0; --i)

58

Page 59: Programacion en Java

putchar(p[i]);

free(p);

}

Este programa lee los primeros 1000 caracteres de la entrada estándar y los imprime en orden inverso. Se puede ver el uso de malloc y de free. En la línea de llamada a malloc hemos introducido una variante: hemos forzado una conversión del tipo devuelto por malloc. Esto lo hemos hecho porque conviene acostumbrarse a asignar a los punteros otros punteros del mismo tipo. Aunque en C estándar se puede asignar un puntero void a cualquier puntero, es conveniente realizar el moldeado del tipo, ya que en C++, el lenguaje C avanzado, esto no est permitido. Muchos compiladores nos avisar n si intentamos asignar un puntero void a otro puntero, aunque nos permiten compilar el programa sin problemas. Los compiladores de C++ directamente paran la compilación. Ya que no nos cuesta mucho hacer el moldeado, es buena costumbre el hacerlo.

Funciones variadic

En C se permite definir funciones con un número variable de argumentos. Son las llamadas funciones variadic. El ejemplo más común de función variadic es la función printf(). En C estándar se define un m‚todo para crear funciones variadic. Para ello se proporciona la cabecera stdarg.h que incluye los tipos y macros que permiten crear dichas funciones.

Declaración de funciones variadic

Una función variadic se declara igual que las demás funciones salvo que en su lista de argumentos aparece en ultimo lugar el símbolo de elipsis (tres puntos). Puede tener otros argumentos adicionales, pero el ultimo siempre es una elipsis. Un ejemplo de declaración variadic es la función printf(), que se declara como:

int printf(char *formato, ...);

En esta declaración observamos que la función printf() necesita al menos un argumento, que debe ser del tipo puntero a carácter y luego un número variable de argumentos. Como la función no conoce a priori el número de argumentos debemos diseñar algún m‚todo de decirle el número de argumentos.

Hay dos m‚todos sencillos. El primero, que es el usado en printf(), es suministrarle a la función la información sobre el número de argumentos en uno de los argumentos. La cadena de formato pasada como primer argumento a printf() le indica los argumentos que van a continuación y el tipo de los mismos.

Otro método usual es utilizar como ultimo argumento uno con significado especial. Por ejemplo en una función que calcule el mayor de los argumentos podemos colocar como ultimo argumento el número 0, o en una función en la que se hagan operaciones con punteros el puntero nulo.

Definición de funciones variadic

Para definir una función variadic debemos seguir los siguientes pasos:

• incluimos como ultimo argumento de la lista de argumentos la elipsis.

59

Page 60: Programacion en Java

• en la lista de variables declaramos una variable de tipo va_list. Este tipo, al igual que las demás macros necesarias est declarado en stdarg.h, por lo que nos debemos asegurar de incluir dicha cabecera.

• ejecutar la macro va_start antes de comenzar a leer los argumentos. La macro va_start necesita dos argumentos. El primero es de tipo va_list, y debe ser la variable que hemos declarado anteriormente. El segundo es el nombre del ultimo argumento que se declara en la lista de argumentos. Hay algunas restricciones en el tipo de este argumento. No es seguro utilizar como ultimo argumento un arreglo, un float o algún tipo que cambie al ser promocionado. Es seguro usar un puntero y un entero normal.

• para in leyendo los argumentos restantes se va ejecutando la macro va_arg sucesivamente. Esta macro va leyendo de la lista de argumentos cada argumento, según el tipo que le proporcionemos. Necesita dos argumentos: el primero es la variable de tipo va_list que habíamos definido y el segundo es un tipo, no una variable. Devuelve el valor del tipo que hemos solicitado.

• cuando hayamos leído todos los argumentos debemos ejecutar la macro va_end, cuyo único argumento es la variable de tipo va_list que hemos definido.

Como ejemplo crearemos una función que imprime todas las cadenas de caracteres que le pasemos como argumentos en la salida estándar:

#include <stdarg.h>

void prints(char *s, ...)

{

char *p;

va_list arg;

va_start(arg, s);

puts(s);

while ((p = va_arg(arg, char *)) != NULL)

puts(s);

va_end(arg);

}

Entrada y salida estándar

Un programa en C se comunica con el usuario y con el sistema a través de las funciones de entrada y salida. Con estas funciones se pueden solicitar y enviar datos al terminal del usuario y a otros programas. Además podemos elegir entre enviar datos binarios o enviarlos como cadenas de texto. Las funciones de entrada y salida en C más habituales son las que forman parte de la llamada "librería estándar". Originalmente esta librería fue implementada para atender las necesidades del sistema operativo UNIX, aunque es habitual encontrarlas en cualquier compilador de C, incluso en sistemas que difieren bastante del UNIX, como son los entornos gráficos y de ventanas.

60

Page 61: Programacion en Java

Entrada y salida de caracteres

En la librería estándar se definen las dos principales vías de comunicación de un programa en C: la entrada estándar y la salida estándar. Generalmente est n ambas asociadas a nuestro terminal de manera que cuando se imprimen datos en la salida estándar los caracteres aparecen en el terminal, y cuando leemos caracteres de la entrada estándar los leemos del teclado del terminal. La entrada y salida estándar trabaja con caracteres (en modo carácter), con datos o números binarios. Es decir, todos los datos que enviemos a la salida estándar deben ser cadenas de caracteres. Por ello para imprimir cualquier dato en la salida estándar primero deberemos convertirlo en texto, es decir, en cadenas de caracteres. Sin embargo esto lo haremos mediante las funciones de librería, que se encargan de realizar esta tarea eficientemente.

Comenzaremos con las dos funciones principales de salida de caracteres: putchar() y getchar(). La función putchar escribe un único carácter en la salida estándar. Su uso en sencillo y generalmente est implementada como una macro en la cabecera de la librería estándar. La función getchar() devuelve el carácter que se halle en la entrada estándar. Esta función tiene dos particularidades. La primera es que aunque se utiliza para obtener caracteres no devuelve un carácter, sino un entero. Esto se hace así ya que con un entero podemos representar tanto el conjunto de caracteres que cabe en el tipo carácter (normalmente el conjunto ASCII de caracteres) como el carácter EOF de fin de fichero. En UNIX es habitual representar los caracteres usando el código ASCII, tanto en su versión de 7 bits como en su versión ampliada a 8 bits. Estos caracteres se suelen representar como un entero que va del 0 al 127 o 256. El carácter EOF entonces es representado con un -1. Además esto también lo aplicaremos cuando leamos los ficheros binarios byte a byte.

Una tercera función de caracteres que no es muy frecuente es la función ungetchar(). Con ella devolvemos al sistema el ultimo carácter que hemos leído con getchar(). No se puede llamar dos veces seguidas a ungetchar. El porqué‚ queda más claro al explicar el uso de ungetchar. Habitualmente cuando leemos un conjunto de caracteres de la entrada estándar le pediremos que sean de un determinado tipo. si por ejemplo queremos leer un dato num‚rico bastar con hacer un bucle que lea números (caracteres numéricos). El bucle normalmente terminar cuando el carácter leído no sea un número. La mejor forma de saber si el siguiente carácter es un número es leerlo. Pero a leerlo, si no es un número ya no estar disponible para futuras lecturas. Aquíí es desde se usa ungetchar(). Una vez que hemos comprobado que no es un número lo devolvemos, y así estar listo para la siguiente lectura.

Visto esto podemos seguir con las funciones gets() y puts(). La función puts() simplemente se imprime una cadena de caracteres en la salida estándar. Le debemos proporcionar la dirección donde encontrar la cadena de caracteres. Como ejemplo vamos a dar una implementacion sencilla de esta función:

void putchar(char *p)

{

while (*p)

putchar(*p++);

}

realmente la función puts es más complicada, pues devuelve un EOF si ha ocurrido algún error.

Para imprimir datos de un modo más general el C dispone de la función printf(), que se ocupa de la impresión formateada en la salida estándar.

61

Page 62: Programacion en Java

La función printf() imprime los datos en la salida estándar según una cadena de control. Est definida en la cabecera estándar stdio.h como:

int printf(const char *formato, ...);

La función printf() tiene varias características peculiares. La primera es que es una función común número variable de argumentos. Normalmente a estas funciones se las llama variadic, y se reconocen porque incluyen en su línea de argumentos el símbolo de elipsis (tres puntos ...). Sólo el primer par metro es obligatorio, y es del tipo puntero constante a carácter. Esta cadena tiene dos funciones: imprimir un mensaje en la salida estándar y formatear los demás argumentos que se la pasan a la función para ser impresos como texto.

Funcionamiento de la función printf()

Si llamamos a la función printf() simplemente con una cadena de caracteres la función fprintf la imprime de modo parecido a como lo hace la función puts(). Por ejemplo:

printf("Hola, mundo\n");

imprime la cadena "Hola, mundo\n" en la salida estándar. Pero además la función printf es capaz de imprimir otros tipos de datos como variables num‚ricas en la salida estándar. Para ello debemos avisar a la función printf() de que le pasamos como argumento una variable, ya que la función no tiene modo alguno de saber si le hemos pasado algún par metro. El modo de hacerlo es insertando códigos de control en la cadena de formato. Estos códigos normalmente van precedidos del carácter %. Por ejemplo el código %d representa enteros en formato decimal. Así la forma de imprimir una variable entera en la salida estándar es:

printf("esto es un entero: %d\n", 10);

Cuando printf() se encuentra el código %d en la cadena de formato lee el siguiente argumento de la función, que debe ser un entero, y lo convierte en su representación decimal como cadena de caracteres. La cadena que representa al número sustituye al código %d de la cadena de formato y se imprime la cadena resultante. Hay una gran variedad de códigos de control para formatear los diferentes tipos de datos. Los más importantes son:

• para imprimir caracteres y cadenas de caracteres:

%c imprime un carácter

%s imprime una cadena de caracteres.

Esta código permite imprimir cadenas sin que printf() mire si la cadena contiene posibles códigos de control.

%% imprime el carácter %

%p imprime la dirección donde apunta un puntero.

El tipo de dato impreso depende de la implementacion. Se suele usar en la depuración de programas o sistemas

• para imprimir enteros

62

Page 63: Programacion en Java

%d imprime un entero en su representación decimal

%u imprime un entero sin signo en su

representación decimal

%x imprime un entero en su representación hexadecimal

%o imprime un entero en su representación octal

%ld imprime un entero largo en su representación decimal

%hd imprime un entero corto en su representación hexadecimal.

Recordar que los enteros cortos se pasan como enteros

• para imprimir números reales (con decimales)

%f imprime un valor del tipo doble precisión como su valor real en simple precisión.

Recordar que los floats se pasan como doubles

%e imprime un valor double en sus representaciones doble precisión. La cadena generada es del tipo

+-ddd.ddd e+-ddd

Estos códigos de control pueden ser en la mayoría de los casos completados con códigos de alineación y de signos. Los códigos de alineación se colocan entre el signo % y el código de control. Los más frecuentes son:

- para justificar por la izquierda una conversión

+ añade un signo + a los valores positivos

' ' (espacio) añade un espacio a los valores con signo que no tengan signo más o menos

# para añadir el prefijo octal 0 en una conversión a octal, o el prefijo 0x en una conversión a hexadecimal

0 rellena con ceros antecedentes en una conversión cuando se ha especificado en ancho que es mayor que el resultado de la conversión

63

Page 64: Programacion en Java

Entre el código de alineación y el código de control podemos insertar un valor de anchura de campo que controla el ancho de la conversión. Por ejemplo:

printf(":%3d:", 4); /* imprime : 3: */

También podemos especificar un valor que controla el número de dígitos decimales en un valor real. Este valor se coloca tras la anchura de campo precedido de un punto. Por ejemplo:

printf("%.3f", 3.99999); /* imprime 3.999 */

Para cadenas de caracteres también podemos insertar un valor que permite escoger cuantos caracteres se imprimen de la cadena. Para ello daremos este valor tras un punto, al igual que hacemos para el valor de precisión. Por ejemplo:

printf("%.4s\n", "Hola, mundo\n"); /* imprime Hola */

La función scanf()

La función scanf() hace el trabajo inverso a la función printf(), es decir, examina la entrada estándar y carga valores en variables. Se define como:

int scanf(const char *formato, ...);

Esta función trabaja de un modo parecido a como lo hace printf(). Necesita una cadena que indica el formato de los datos que se deben leer. La cadena de formato no se imprime, sino que sólo sirve para que scanf() determine el tipo de datos a leer. El resto de los argumentos deben ser punteros a las variables donde se deben almacenar los datos leídos. Por ejemplo:

scanf("%d", &i);

lee un entero en formato decimal y lo almacena en la variable i. Hay que tener cuidado de pasar siempre punteros a scanf(), por lo que para guardar datos en variables normales deberemos emplear el operador dirección &. Los códigos de control son Análogos a los de printf, es decir, %d., %e, %s , ...

La función scanf() es bastante sensible a los errores. Si el usuario introduce los datos incorrectamente la función scanf() simplemente falla.

Si queremos realizar una función de lectura más robusta podemos realizar lo siguiente:

• leemos la entrada en un arreglo de caracteres. Para ello simplemente usaremos la función gets()

• exploramos el arreglo de caracteres manualmente paso a paso. Para ello podemos usar la función sscanf().

La función sscanf se define como:

int sscanf(const char *s, const char *formato, ...);

64

Page 65: Programacion en Java

y realiza una tarea parecida a scanf(), pero explorando la cadena apuntada por s en vez de la entrada estándar. De este modo podemos ir explorando la cadena leída previamente con gets() paso a paso e informando al usuario del lugar donde ha cometido un error al introducir los datos.

La función scanf salta los espacios precedentes cuando se lee un entero. Si la función no ha terminado de leer los datos pedidos espera a leer una nueva línea de la entrada estándar. Esto lo hace así porque para efectos de formato la función scanf() coincidirá al carácter de nueva línea y al carácter de tabulador como un espacio en blanco, y la función scanf() salta los espacios en blanco. Este efecto de nuevo se puede evitar con el procedimiento anteriormente descrito.Ficheros

El sistema de ficheros habitual en C estándar est enfocado al sistema operativo UNIX, aunque otros sistemas operativos ofrecen con los compiladores de C una librería de funciones que emula en mayor o menor grado a la implementacion UNIX.

El sistema de ficheros de UNIX

El sistema UNIX organiza el sistema de ficheros en directorios. Cada directorio puede contener ficheros y otros directorios. Cada archivo o subdirectorio est identificado por un nombre y una extensión opcional. Es sistema UNIX considera a los dispositivos también como archivos, de manera que se utilizan las mismas funciones para escribir y leer de los dispositivos que las empleadas con ficheros habituales.

Los archivos se manejan con la librería estándar del C mediante las estructuras de archivo FILE. Al ejecutar un programa en UNIX el sistema provee automáticamente tres archivos al programa: la entrada estándar, la salida estándar y la salida de error estándar. El usuario no necesita realizar ninguna operación previa de apertura para emplearlos. De hecho las funciones de entrada y salida estándar envían y recogen datos a través de estos ficheros.

Apertura y cierre de un fichero

Para abrir un fichero primero debemos crear una variable de tipo puntero a FILE. Este puntero permitir realizar las operaciones necesarias sobre el fichero. Este puntero deber apuntar a una estructura de tipo FILE. Estas estructuras son creadas por el sistema operativo al abrir un fichero. Para poder inicializar nuestro puntero a fichero bastar llamar a la función fopen(). Esta función intenta abrir un fichero. Si tiene éxito crear una estructura de tipo FILE y devuelve un puntero a FILE que apunta a la estructura creada. En caso de no poder abrir el fichero devuelve en puntero nulo. La función fopen() se define en la cabecera estándar stdio.h como:

FILE *fopen( const char * filename, const char *modo);

Necesita dos argumentos del tipo puntero a carácter. Cada uno de ellos debe apuntar a una cadena de caracteres. El primero indica el nombre del fichero a abrir. En UNIX y otros sistemas se puede especificar con el nombre del fichero el directorio donde se abrir el fichero. El segundo indica el modo en el que se abrir el fichero. Hay que tener cuidado en pasar un puntero a cadena de caracteres y no un solo carácter. Es fácil cometer la equivocación de pasar como segundo argumento un carácter 'r' en vez de la cadena "r". Los modos más frecuentes de abrir un fichero son:

"r" Abre un fichero de texto que existía previamente para lectura.

"w" Crea un fichero de texto para escritura si no existe el fichero con el nombre especificado, o trunca (elimina el anterior y crea uno nuevo) un fichero anterior

65

Page 66: Programacion en Java

"a" Crea un fichero de texto si no existe previamente o abre un fichero de texto que ya existía para añadir datos al final del fichero. Al abrir el fichero el puntero del fichero queda posicionado a

"rb" Funciona igual que "r" pero abre o crea el fichero en modo binario.

"wb" Análogo a "w" pero escribe en un fichero binario.

"ab" Análogo a "a" pero añade datos a un fichero binario.

"r+" Abre un fichero de texto ya existente para lectura y escritura.

"w+" Abre un fichero de texto ya existente o crea uno nuevo para lectura y escritura.

"a+" Abre un fichero de texto ya existente o crea un fichero nuevo para lectura y escritura. El indicador de posición del fichero queda posicionado al final del fichero an

"r+b" ó "rb+" Funciona igual que "r+" pero lee y escribe en un fichero binario.

"w+b" ó "wb+" Análogo a "w+" pero en modo binario.

"a+b" ó "ab+" Análogo a "a+" pero en modo binario.

Una llamada típica a la función fopen() es la siguiente:

FILE *fp;

if (( fp = fopen( "mifichero", " r")) = = NULL)

perror( "No puedo abrir el fichero mifichero\n");

/* imprime un mensaje de error */

Para cerrar un fichero basta llamar a la función fclose que se define en

stdio.h como:

int fclose(FILE *fichero);

Su argumento es un puntero a una estructura FILE asociada a algún fichero abierto. Esta función devuelve 0 en caso de éxito y EOF en caso de error.

Lectura y escritura sobre un fichero

Para leer y escribir en un fichero en modo texto se usan funciones análogas a las de lectura y escritura de la entrada y salida estándar. La diferencia estriba en que siempre deberemos dar un puntero a FILE para indicar sobre que fichero efectuaremos la operación, ya que podemos tener simultáneamente abiertos varios ficheros. Las funciones que trabajar con ficheros tienen nombres

66

Page 67: Programacion en Java

parecidos a las funciones de entrada y salida estándar, pero comienzan con la letra f. Las más habituales son:

int fprintf( FILE *fichero, const char *formato, ... );

/* trabaja igual que printf() sobre el fichero */

int fscanf( FILE *fichero, const char *formato, ... );

/* trabaja igual que scanf() sobre el fichero */

int fputs( const char *s, FILE *fichero );

/* escribe la cadena s en el fichero */

int fputc(int c, FILE *fichero);

/* escribe el carácter c en el fichero */

int fgetc( FILE *fichero);

/* lee un carácter del fichero */

char *fgets( char *s, int n, FILE * fichero);

/* lee una línea del fichero */

Hay una equivalencia entre las funciones de lectura y escritura estándar y las funciones de lectura y escritura de ficheros. Normalmente las funciones de lectura y escritura estándar se definen en la cabecea estándar como macros. Así la línea:

printf("hola\n");

es equivalente a la escritura en el fichero stdout:

fprintf(stdout, "hola\n");

A los ficheros stdin y stdout normalmente accederemos con las funciones de lectura y escritura estándar. Estos ficheros son automáticamente abiertos y cerrados por el sistema. Para escribir en la salida de error estándar deberemos usar las funciones de ficheros con el fichero stderr. Normalmente en UNIX se redirige la salida de error estándar a la impresora. Esta salida de error es muy útil en los procesos por lotes y cuando se usan filtros. Un filtro es simplemente un programa que lee datos de la entrada estándar, los procesa y los envía a la salida estándar. Por ello es conveniente que no se mezclen los mensajes de error con el resultado del proceso. Un ejemplo de filtro sería un programa que expande los caracteres de tabulación en espacios en blanco. Si el programa se llama convierte y queremos procesar el fichero mifichero, debemos escribir la línea:

cat mifichero | convierte > nuevofichero

67

Page 68: Programacion en Java

Hemos usado los mecanismos del UNIX de redireccion (> envía la salida estándar de un programa a un fichero), de tubería ( | conecta la salida estándar de un programa con la entrada estándar de otro) y la utilidad cat, que envía un fichero a la salida estándar.

Lectura y escritura de datos binarios

Para leer y escribir grupos de datos binarios, como por ejemplo arreglos y estructuras, la librería estándar provee dos funciones: fread() y fwrite().

Se declaran en stdio.h como:

size_t fread(void *p, size_t longitud, size_t numelem, FILE *fichero);

size_t fwrite(void *p, size_t longitud, size_t numelem, FILE *fichero);

La función fread() lee del fichero pasado como ultimo argumento un conjunto de datos y lo almacena en el arreglo apuntado por p. Debemos especificar en longitud la longitud del tipo de datos a leer y en numelem el número de datos a leer. La función fwrite() se comporta igual que fread() pero escribe los datos desde la posición apuntada por p en el fichero dado. Como siempre para usar estas funciones debemos abrir el fichero y cerrarlo después de usarlas. Por ejemplo para leer un arreglo de 100 enteros:

int arreglo[100];

FILE *fp;

fp = fopen("mifichero", "rb");

fread(arreglo, sizeof(int), 100, fp);

fclose(fp);

Estas funciones devuelven el número de elementos leídos. Para comprobar si ha ocurrido un error en la lectura o escritura usaremos la función ferror(FILE *fichero), que simplemente devuelve un valor distinto de 0 si ha ocurrido un error al leer o escribir el fichero pasado como argumento.

Al escribir datos binarios en un fichero debemos tener en cuenta consideraciones de portabilidad. Esto es debido a que el orden en que se almacenan los bytes que componen cada tipo de datos en la memoria puede variar de unos sistemas a otros, y las funciones fread() y fwrite() los leen y escriben según est n en la memoria.

Operaciones especiales con los ficheros

Para comprobar si hemos alcanzado el fin de fichero, por ejemplo cuando leemos un fichero binario con fread(), podemos emplear la función feof(), que se define en stdio.h como:

int feof( FILE *fichero);

Esta función devuelve un 0 si no se ha alcanzado el fin de fichero y un valor distinto de 0 si se alcanzó el fin de fichero.

68

Page 69: Programacion en Java

Para comprobar si ha ocurrido un error en la lectura o escritura de datos en un fichero disponemos de la función ferror, que se declara en stdio.h como:

int ferror( FILE *fichero);

Esta función devuelve un valor distinto de 0 si ha ocurrido algún error en las operaciones con el fichero y un 0 en caso contrario. Estas dos funciones trabajan leyendo los indicadores de fin de fichero y error de la estructura FILE asociada a cada fichero. Podemos limpiar ambos indicadores utilizando la función clearerr(), que se define en stdio.h como:

void clearerr( FILE *fichero);

Posicionamiento del indicador de posición del fichero

Cuando se manejan ficheros de acceso aleatorio se necesita poder colocar el indicador de posición del fichero en algún punto determinado del fichero.

Para mover el puntero del fichero la librería estándar proporciona la función fseek(), que se define en stdio.h como:

int fseek( FILE *fichero, long desplazamiento, int modo);

La función devuelve un 0 si ha tenido éxito y un valor diferente en caso de error. El argumento desplazamiento señala el número de caracteres que hay que desplazar el indicador de posición. Puede ser positivo o negativo, o incluso 0, ya que hay tres modos diferentes de desplazar el indicador de posición. Estos modos se indican con el argumento modo. En stdio.h se definen tres macros que dan los posibles modos. La macro SEEK_SET desplaza al indicador de posición desde el comienzo del fichero. La macro SEK_CUR desplaza el indicador de posición desde la posición actual y la macro SEEK_END desplaza al indicador de posición desde el final del fichero. Para este ultimo modo deberemos usar un valor de desplazamiento igual o menor que 0.

Para ver en que posición se halla el puntero del fichero podemos usar la función ftell(), que se define en stdio.h como:

long ftell( FILE *fichero);

Para un fichero binario ftell() devuelve el número de bytes que esté desplazado el indicador de posición del fichero desde el comienzo del fichero.

Además para llevar el indicador de posición al comienzo del fichero tenemos la función rewind(), que se define en stdio.h como:

void rewind( FILE * fichero);

Esta función simplemente llama a fseek(fichero, 0L, SEEK_SET) y luego limpia el indicador de error.

Entrada y salida con tampón

Cuando la librería estándar abre un fichero le asocia un tampón (buffer) intermedio, que permite agilizar las lecturas y escrituras. Así cada vez que se lee un dato del fichero primero se leen datos hasta llenar el tampón, y luego se van leyendo del tampón a medida que se solicitan. De este modo

69

Page 70: Programacion en Java

se accede al sistema de ficheros más eficientemente, ya que para leer un grupo de datos sólo se efectúan unas pocas lecturas del fichero. Este proceso se repite para la escritura de datos. Cuando se envían a un fichero se van almacenando temporalmente en el tampón y cuando se llena el tampón se escribe su contenido en el fichero. Esto plantea varios problemas. El primero es que siempre se debe vaciar el tampón cuando se cierra el fichero. Esto lo hace automáticamente la función fclose(). Sin embargo las demás funciones no lo hacen. Por ello si estamos escribiendo datos en un fichero de lectura y escritura y queremos leer datos, primero debemos vaciar el tampón, para que los datos que leamos están actualizados. Para ello la librería estándar proporciona la función fflush(), que se define en stdio.h como:

int fflush( FILE *fichero);

Esta función devuelve un 0 si tiene éxito y EOF en caso de error. Además si fichero es un puntero nulo entonces fflush() vacía los tampones de todos los fichero abiertos para escritura.

Para alterar el tamaño del tampón de un fichero podemos llamar a la función setvbuf() inmediatamente después de abrir el fichero. Esta función se define en stdio.h como:

int setvbuf(FILE *flujo, char *buffer, int modo, size_t longitud);

El argumento longitud fija el tamaño del tampón. Es argumento nos indica el tipo de tampón elegido. Hay tres tipos: tampón de línea, tampón completo y sin tampón. Para especificar estos tres tipos de tampones se definen en stdio.h las macros _IOFBF (que indica tampón completo), _IOLBF (que indica tampón de línea) y _IONBF ( que indica que el fichero no tiene tampón ). El argumento buffer apunta al lugar donde queremos que est‚ el tampón. Si pasamos como argumento buffer un puntero nulo el sistema se encargar de reservar el lugar del tampón y lo liberar al cerrar el fichero.

Podemos asignar a un fichero un tamaño de tampón grande para que el sistema realice menor número de operaciones de lectura y escritura. si el fichero es interactivo( por ejemplo un terminal) quizás nos ser más útil ajustar el tampón al modo de línea o incluido eliminar el tampón. Deberemos probar diferentes valores y modos para así determinar el mejor tampón a usar.

Operaciones misceláneas con ficheros

La librería estándar proporciona algunas funciones adicionales para manejar ficheros. Por ejemplo la función remove(), que se define en stdio.h como:

int remove(const char *nombrefichero);

Esta función elimina el fichero de nombre nombrefichero. Conviene cerrar el fichero antes de eliminarlo. También disponemos de una función para renombrar el fichero. La función rename(), definida en stdio.h como:

int rename(const char *antiguo, const char *nuevo);

intenta renombrar al fichero de nombre antiguo. si tiene éxito devuelve un 0. Hay que asegurarse antes de que no existía un fichero de nombre nuevo.

Otra función para abrir ficheros es freopen(), que se define en stdio.h como:

70

Page 71: Programacion en Java

FILE *freopen( const char *nombre, const char *modo, FILE *fichero);

Esta función cierra el fichero pasado como tercer argumento y lo abre con el nuevo nombre y modo especificado. Devuelve un puntero a FILE que apunta al nuevo fichero abierto, o un puntero nulo en caso de error, tal y como lo hace fopen().

El Preprocesador de C

El preprocesado es una parte de la compilación en la que se hacen algunas tareas sencillas. Las fundamentales son:

• supresión de comentarios.• expansión de macros.• inclusión del código de las cabeceras.• conversión de las secuencias de escape en caracteres dentro de cadenas de caracteres y

de constantes de tipo carácter.

El preprocesado puede ser de dos tipos: externo (lo realiza un programa adicional) o interno(se preprocesa y compila a la vez. En UNIX el preprocesado es externo, ya que lo hace el programa cpp, que es ejecutado automáticamente por el compilador cc. Es bastante instructivo preprocesar un fichero y revisar el código fuente resultante.

Directivas de preprocesado

Para realizar las diferentes acciones que admite el preprocesado disponemos de una serie de directivas de preprocesado, que son como comandos que instruyen al Preprocesador para realizar las expansiones. Todas las directivas del Preprocesador comienzan con el carácter # seguida del nombre de comando. El signo # debe estar al comienzo de una línea, para que el Preprocesador lo pueda reconocer. La más sencilla de las directivas es #include. Esta directiva debe ir seguida de un nombre de fichero. El nombre debe ir entrecomillado o encerrado entre signos de mayor y menor. Lo que hace el Preprocesador es sustituir la línea donde se halla la directiva por el fichero indicado. Por ejemplo:

#include <stdio.h>

#include "stdio.h"

La diferencia entre encerrar el nombre del fichero entre comillas o entre signos de mayor y menor es que al buscar el fichero con las comillas la búsqueda se hace desde el directorio actual, mientras que entre signos de mayor y menor la búsqueda se hace en un directorio especial. Este directorio varía con la implementacion, pero suele estar situado en el directorio del compilador. El Preprocesador y el compilador ya conocen donde se ubica el directorio. Todas las cabeceras estándar se hallan en ese directorio.

Se puede incluir cualquier tipo de fichero fuente, pero lo habitual es incluir sólo ficheros de cabecera.

Hay que tener en cuenta que el fichero incluido es preprocesado. Esto permite expandir algunos tipos de macros y ajustar la cabecera al sistema mediante las directivas de preprocesado. Para ello se suelen usar macros que actúan como banderas.

Definición de macros

71

Page 72: Programacion en Java

En C una macro es un identificador que el Preprocesador sustituye por un conjunto de caracteres. Para definir una macro se dispone de la directiva #define. Su sintaxis es:

#define identificador conjunto de caracteres

Se utiliza habitualmente en los ficheros de cabecera para definir valores y constantes. Por ejemplo:

#define EOF -1

#define SEEK_SET 0

#define BUFSIZ 512

Otro uso muy normal en los ficheros de cabecera emplear un símbolo definido con #define como bandera (selector para condiciones), utilizándolo con la directiva #ifdef.

Una macro definida con #define no se puede redefinir a menos que la siguiente definición coincida con la primera exactamente. Para redefinir una macro primero debemos eliminarla con la directiva #undef. Debemos acompañar a la directiva #undef con el nombre de la macro.

Estilo de programación: normas de indentacion

Definición de las normas de indentacion que se usar n a lo largo del curso en los programas en C. Se seguir n normas muy parecidas a las empleadas habitualmente por otros autores en libros recientes de programación, con algunas variaciones propias.

Estilo y disposición general del programa.

Cada archivo contendrá en primer lugar un comentario donde se explicar brevemente el cometido del mismo. Por ejemplo:

/*

* HOLA.C Este programa imprime en mensage de saludo en la salida

* estándar.

*/

A continuación ir una lista de las cabeceras que se incluir n en ‚l:

#include <stdio.h>

#include <stdlib.h>

#include "hola.h"

A continuación la lista de variables globales:

int i = 0,j,numero = 0,num2;

72

Page 73: Programacion en Java

char c,temp;

A continuación la lista de declaración de funciones, si no se usa un fichero de cabecera para este propósito.

void impmensage(char *mensage);

FILE *pidefichero(char *mensage);

A continuación la función main() y luego las demás.

main(argc, argv)

int argc;

char *argv[];

{

lista de variables locales;

lista de sentencias;

}

Escritura de funciones

Se comienza en la primera columna. Primero va el tipo de la función. Luego el nombre y la lista de argumentos. Entre el nombre y el paréntesis de la lista de argumentos no se deja espacios en blanco. Detraes de cada coma que separa los argumentos se deja un espacio en blanco. Al final se cierra el paréntesis.

A continuación va la declaración de los tipos de los argumentos. Cada declaración de tipo de argumento va en una línea, precedida de un tabulador.

A continuación se abren las llaves de la función. Luego el cuerpo de la función, separando la lista de variables y la lista de declaración de las funciones de las demás sentencias. Todas ellas tienen como mínimo un tabulador a la izquierda, no pudiendo empezar en la primera columna. Se finaliza con la llave de cierre.

char *función1(arg1, arg2, arg3)

int arg1;

char *arg2;

struct *sutipo arg3;

{

int i,

73

Page 74: Programacion en Java

j;

char *puntero;

setencia1;

.

.

sentencian;

}

Declaración de variables

Se tratar n de colocar por tipos en columnas, procurando que coincidan las comas y los puntos y comas:

char carct1,c;

Podremos incluir una breve explicación de su uso:

char temp; /* variable temporal */

Bucle while

Si consta de una sola sentencia se separa la línea de la expresión de la línea de la sentencia. Se deja un espacio en blanco entre la palabra while y el paréntesis de la expresión:

while (expresión)

sentencia;

Si el bucle consta de un bloque de sentencias se añade la llave de comienzo de bloque detraes de la expresión, en la misma línea. Las diferentes líneas del bloque las colocaremos debajo de la línea de la expresión, colocándoles un tabulador al principio de cada línea. Por ultimo, a la misma altura que las sentencias del bloque, colocaremos la llave de cierre:

while (expresión) { /* comentario muy breve */

sentencia1;

sentencia2;

}

Sentencia if/else

Tras la palabra if, un espacio en blanco y la expresión entre paréntesis.

74

Page 75: Programacion en Java

La palabra else, si la hay, va a la misma altura que la palabra if.

Como norma general seguiremos las mismas convenciones que para el bucle while en el asunto de los bloques y las sentencias, tanto para la para if como para else:

if (expresión)

sentencia1;

else

sentecia2;

o también:

if (expresión) {

sentencia1;

sentencia2;

}

else {

sentencia3;

sentencia4;

}

Forma general de un programa en C

Declaraciones globales

main( )

{

variables locales

sentencias

}

f1( )

{

.........

75

Page 76: Programacion en Java

}

...

...

fn ( )

{

.........

}

Nombre de identificadores

Son los nombres usados para referirse a las variables, funciones, etiquetas y otros objetos definidos por el usuario.La longitud de un identificador en Turbo C puede variar entre 1 y 32 caracteres. El primer carácter debe ser una letra o un símbolo de subrayado, los caracteres siguientes pueden ser letras, números o símbolos de subrayado.

Correcto: cont, cuenta23, balance_total

Incorrecto: 1cont, hola!, balance...total

En C las mayúsculas y las minúsculas se tratan como distintas

Tipos de datos

Existen cinco tipos de datos atómicos:

Tipo bits rango

char 8 0 a 255

int 16 -32.768 a 32.767

float 32 3,4 E -38 a 3,4 E +38

double 64 1,7 E -308 a 1,7 E +308

void 0 sin valor

(*) El void se usa para declarar funciones que no devuelven ningún valor o para declarar funciones sin parámetros.

Modificadores de tipos

signed

unsigned

long

76

Page 77: Programacion en Java

short

Los modificadores signed, unsigned, long y short se pueden aplicar a los tipos base entero y carácter. Sin embargo, long también se puede aplicar a double.

Tipo bits Rango

char 8 -128 a 127

unsigned char 8 0 a 255

Signed char 8 -128 a 127

int 16 -32.768 a 32.767

unsigned int 16 0 a 65.535

signed int 16 -32.768 a 32.767

short int 16 -32.768 a 32.767

unsigned short int 16 0 a 65.535

signed short int 16 -32.768 a 32767

long int 32 -2147483648 a 2147483647

signed long int 32 -2147483648 a 2147483647

float 32 3,4 E -38 a 3,4 E +38

double 64 1,7 E -308 a 1,7 E +308

long double 64 1,7 E -308 a 1,7 E +308

Modificadores de acceso

Las variables de tipo const no pueden ser cambiadas durante la ejecución del programa. Por ejemplo,

const int a;

Declaración de variables

Todas las variables han de ser declaradas antes de ser usadas. Forma general:

tipo lista_de_variables; int i,j,l;

short int si;

Existen tres sitios donde se pueden declarar variables: dentro de las funciones (variables locales), en la definición de parámetros de funciones (parámetros formales) y fuera de todas las funciones (variables globales).

Variables externas

Si una función situada en un fichero fuente desea utilizar una variable de este tipo declarada en otro fichero, la debe declarar (o mejor dicho referenciar) con la palabra extern.

Archivo 1 Archivo 2

77

Page 78: Programacion en Java

int x,y; extern int x,y;

char ch; extern char ch;

main ( ) void func1( )

{ {

x=120; x=y/10;

. . . . . . . . . . . . .

} }

Variable estáticas (static)

Tienen memoria asignada durante toda la ejecución del programa. Su valor es recordado incluso si la función donde está definida acaba y se vuelve a llamar más tarde. Ejemplo:

series (void)

{

static int num;

num=num+23;

return (num);

}

Variables registro

El especificador register pide a Turbo C que mantenga el valor de una variable con ese especificador de forma que se permita el acceso más rápido a la misma. Para enteros y carácteres esto significa colocarla en un registro de la CPU.

Sólo se puede aplicar a variables locales y a los parámetros fomales de una función.

Son ideales para el control de bucles.

pot_ent (int m, register int e)

{

register int temp;

temp=1;

for ( ; e; e--) temp *=m;

78

Page 79: Programacion en Java

return (temp);

}

Sentencias de asignación

Forma general: nombre_variable = expresion;

Abreviaturas en C

x=x+10 <----------> x+=10

x=x-10 <----------> x-=10

Conversión de tipos

Se da cuando se mezclan variables de un tipo con variables de otro tipo.

El valor de la derecha de la asignación se convierte al tipo del lado izquierdo.

Puede haber pérdida de los bits más significativos en un caso como: short = long

Inicialización de variables

Tipo nombre_variable = constante;

char c='a';

int primero=0;

float balance=123.23;

Todas las variables globales se inicializan a cero sino se especifica otro valor inicial. Las variables locales y register tendrán valores desconocidos antes de que se lleve a cabo su primera asignación.

Constantes

Tipo dato Ejemplo de constantes

char 'a' '\n' '9'

int 1 123 -234

float 123.23

Una constante de tipo cadena de caracteres está constituida por una secuencia de caracteres entre comillas dobles "Hola".

Carácteres con barra invertida

79

Page 80: Programacion en Java

\n Nueva línea

\t Tabulación horizontal

\b Espacio atrás

\r Retorno de carro

\f Salto de página

\\ Barra invertida

\' Comilla simple

\" Comilla doble

Operadores

En C hay tres clases de operadores: aritméticos, relacionales y lógicos, y a nivel de bits.

Aritméticos

- resta

+ suma

* producto

/ división

% módulo (resto de la división entera)

-- decrementar

++ incrementar

x=10; x=10;

y=++x; y=x++;

y=11 y=10

Relacionales

En C cierto es cualquier valor distinto de cero. Falso es cero.

> mayor que

>= mayor o igual que

80

Page 81: Programacion en Java

< menor que

<= menor o igual que

== igual

!= distinto

Lógicos

&& y

|| o

! no

El operador ?

Exp 1 ? Exp 2 : Exp 3

Se evalúa exp1 si es cierto se evalúa exp2 y toma ese valor para la expresión. Si exp1 es falso evalúa exp3 tomando su valor para la expresión.

Ejemplo:

x=10:

y=x>9 ? 100 : 200 --------> y = 100

Los operadores de punteros & y *

& devuelve la dirección de memoria del operando.

Ejemplo:

m=&cont; coloca en m la dirección de memoria de la variable cont

& (la dirección de)

* devuelve el valor de la variable ubicada en la dirección que se especifica.

Ejemplo:

q=*m; coloca el valor de cont en q. *(en la dirección)

Sizeof

Es un operador monario que devuelve la longitud, en bytes, de la variable o del especificador de tipo al que precede.

81

Page 82: Programacion en Java

Ejemplo:

flat f;

printf ("%f",sizeof f); Mostrara 4

printf ("%d", sizeof (int)); Mostrara 2

El nombre del tipo debe ir entre paréntesis.

ESTRUCTURAS CONDICIONALES

If

if (expresion) {

............

............

}

else {

...........

...........

}

Switch

switch (variable) {

case cte1 :

...........

...........

break;

case cte2 :

...........

...........

break;

..................

82

Page 83: Programacion en Java

..................

default :

...........

...........

}

Switch sólo puede comprobar la igualdad.

BUCLES

For

for (inicialización; condición; incremento) sentencia

inicialización -> asignación

condición -> expresión relacional

Ejemplo: for (x=1; x<=100; x++) printf ("%d",x); Imprime los números del 1 al 100

While

while (condición) sentencia;

Ejemplo:

while (c!='A') c=getchar( );

Do / While

Analiza la condición al final.

do {

...........

...........

} while (condición);

Break

Tiene dos usos:

• para finalizar un case en una sentencia switch. • para forzar la terminación inmediata de un bucle.

83

Page 84: Programacion en Java

Exit

Para salir de un programa anticipadamente. Da lugar a la terminación inmediata del programa, forzando la vuelta al S.O. Usa el archivo de cabecera stdlib.h

Ejemplo:

#include <stdlib.h>

main (void)

{

if (!tarjeta_color( )) exit(1);

jugar( );

}

Continue

Hace comenzar la iteración siguiente del bucle, saltando así la secuencia de instrucciones comprendida entre el continue y el fin del bucle.

do {

scanf("%d",&num);

if (x<0) continue;

printf("%d",x);

} while (x!=100);

Funciones

tipo nombre_funcion (lista de parametros)

{

............

............

}

tipo, especifica el tipo de valor que devuelve la sentencia return de la función.

Llamada por valor

84

Page 85: Programacion en Java

Copia el valor de un argumento en el parámetro formal de la subrutina. Los cambios en los parámetros de la subrutina no afectan a las variables usadas en la llamada.

int cuad (int x);

main ( ) cuad (int x)

{ {

int t=10; x=x*x;

printf ("%d %d",cuad(t),t); return(x);

return 0; }

}

Salida es << 100 10 >>

Llamada por referencia

Es posible causar una llamada por referencia pasando un puntero al argumento. Se pasa la dirección del argumento a la función, por tanto es posible cambiar el valor del argumento exterior de la función.

int x,y; inter (int *x,int *y)

inter (&x,&y); {

int temp;

temp=*x;

*x=*y;

*y=temp;

}

Arrays

Todos los arrays tienen el 0 como índice de su primer elemento.

char p [10]; array de carácteres que tiene 10 elementos, desde p[0] hasta p[9].

Para pasar arrays unidimensionales a funciones, en la llamada a la función se pone el nombre del array sin índice.

Ejemplo:

85

Page 86: Programacion en Java

main ( ) Si una función recibe un array unidimensional, se puede

{ declarar el parámetro formal como un puntero, como un

int i[10]; array delimitado o como un array no delimitado.

func1 (i);

}

func1 (int *x) /puntero/

func1 (int x[10]) /array delimitado/

func1 (int x[ ]) /array no delimitado/

Inicialización de arrays

Forma general de inicialización de un array:

tipo nombre_array [tamaño] = {lista de valores};

lista de valores, es una lista de constantes separadas por comas, cuyo tipo es compatible con el tipo del array. La primera constante se coloca en la primera posición del array, la segunda constante en la segunda posición y así sucesivamente.

Ejemplo:

int i[10]={1,2,3,4,5,6,7,8,9,10};

Los arrays de carácteres que contienen cadenas permiten una inicialización de la forma:

char nombre_array [tamaño]="cadena";

Se añade automáticamente el terminador nulo al final de la cadena.

Ejemplo

char cad[5]="hola"; equivalentes char cad[5]={'h','o','l','a','\o'};

Es posible que C calcule automáticamente las dimensiones de los arrays utilizando arrays indeterminados. Si en la inicialización no se especifica el tamaño el compilador crea un array suficientemente grande para contener todos los inicializadores presentes.

char e1[ ]="error de lectura \n";

Cadenas

86

Page 87: Programacion en Java

Aunque C no define un tipo cadena, estas se definen como un array de carácteres de cualquier longitud que termina en un carácter nulo ('\0').

Array que contenga 10 caracteres: char s[11];

Una constante de cadena es una lista de caracteres encerrada entre dobles comillas.

Funciones de manejo de cadenas

Archivo de cabecera string.h

char *strcpy (char *s1, const char *s2); copia la cadena apuntada por s2 en la apuntada por s1. Devuelve s1.

char *strcat (char *s1, consta char *s2); concatena la cadena apuntada por s2 en la apuntada por s1, devuelve s1.

int strlen (const char *s1); devuelve la longitud de la cadena apuntada por s1.

int strcmp (const char *s1, const char *s2); compara s1 y s2, devuelve 0 si con iguales, mayor que cero si s1>s2 y menor que cero si s1<s2. Las comparaciones se hacen alfabéticamente.

Arrays Bidimensionales

Se declaran utilizando la siguiente forma general:

tipo nombre_array [tamaño 2ª dim] [tamaño 1ª dim];

Ejemplo -> int d [10][20];

Cuando se utiliza un array bidimensional como argumento de una función realmente sólo se pasa un puntero al primer elemento, pero la función que recibe el array tiene que definir al menos la longitud de la primera dimensión para que el compilador sepa la longitud de cada fila.

Ejemplo: función que recibe un array bidimensional de dimensiones 5,10 se declara así:

func1 (int x[ ][10])

{

....................

}

Arrays y Punteros

Un nombre de array sin índice es un puntero al primer elemento del array.

Ejemplo: Estas sentencias son idénticas:

87

Page 88: Programacion en Java

char p[10]; - p

- &p[0]

int *p, i[10];

p=i; ambas sentencias ponen el valor 100 en el sexto elemento de i.

i[5]=100;

*(p+5)=100;

Esto también se puede aplicar con los arrays de dos o más direcciones.

int a[10][10];

a=&a[0][0];

a[0][4]=*((*a)+4);

Memoria dinámica

• Malloc (n) reserva una porción de memoria libre de n bytes y devuelve un puntero sobre el comienzo de dicho espacio.

• Free (p) libera la memoria apuntada con el puntero p.

Ambas funciones utilizan el archivo de cabecera stdlib.h

Si no hay suficiente memoria libre para satisfacer la petición, malloc ( ) devuelve un nulo.

Ejemplo:

char *p;

p=malloc(1000);

Estructuras

La forma general de una definición de estructura es:

struct etiqueta {

tipo nombre_variable;

tipo nombre_variable;

.............................

.............................

88

Page 89: Programacion en Java

} variables _de_estructura

Ejemplo:

struct dir {

char nombre[30];

char calle[40];

char ciudad[20];

char estado[3];

unsigned long int codigo;

} info_dir;

A los elementos individuales de la estructura se hace referencia utilizando . (punto).

Ejemplo:

info_dir.codigo = 12345;

Forma general es:

nombre_estructura.elemento

Una estructura puede inicializarse igual que los vectores:

struct familia {

char apellido[10];

char nombrePadre[10];

char nombreMadre[10];

int numerohijos;

} fam1={"Garcia","Juan","Maria",7};

Arrays de estructuras

Se define primero la estructura y luego se declara una variable array de dicho tipo.

Ejemplo:

struct dir info_dir [100];

Para acceder a una determinada estructura se indexa el nombre de la estructura:

89

Page 90: Programacion en Java

info_dir [2].codigo = 12345;

Paso de estructuras a funciones

Cuando se utiliza una estructura como argumento de una función, se pasa la estructura íntegra mediante el uso del método estándar de llamada por valor.

Ejemplo:

struct tipo_estructura {

int a,b;

char c;

};

void f1 (struct tipo_estructura param);

main ( )

{

struct tipo_estructura arg;

arg.a = 1000;

f1(arg);

return 0;

}

void f1 (struct tipo_estructura param)

{

printf ("%d",param.a);

}

Punteros a estructuras

Declaración:

struct dir * pruntero_dir;

Existen dos usos principales de los punteros a estructuras:

• Para pasar la dirección de una estructura a una función. • Para crear listas enlazadas y otras estructuras de datos dinámicas.

90

Page 91: Programacion en Java

Para encontrar la dirección de una variable de estructura se coloca & antes del nombre de la estructura.

Ejemplo:

struct bal {

float balance;

char nombre[80];

} persona;

struct bal *p;

p = &persona; (coloca la dirección de la estructura persona en el puntero p)

No podemos usar el operador punto para acceder a un elemento de la estructura a través del puntero a la estructura. Debemos utilizar el operador flecha ->

p -> balance

Tipo enumerado

enum identificador {lista de constantes simbólicas};

Ejemplo:

enum arcoiris {rojo, amarillo, verde, azul, blanco};

(realmente asigna rojo=0, amarillo=1, ...)

printf ("%d %d", rojo, verde); imprime 0 2 en pantalla

Podemos especificar el valor de uno o más símbolos utilizando un inicializador. Lo hacemos siguiendo el símbolo con un signo igual y un valor entero.

enum moneda {penique, niquel, diez_centavos, cuarto=100, medio_dolar, dolar};

Los valores son: penique 0, niquel 1, diez_centavos 2, cuarto 100, medio_dolar 101, dolar 102

Punteros

int x=5, y=6;

int *px, *py;

91

Page 92: Programacion en Java

px=py; copia el contenido de py sobre px, de modo que px apuntará al mismo objeto que

apunta py.

*px=*py; copia el objeto apuntado por py a la dirección apuntada por px.

px=&x; px apunta a x.

py=0; hace que py apunte a nada (NULL).

px++; apunta al elemento siguiente sobre el que apuntaba inicialmente

Ejemplo:

Se puede sumar o restar enteros a y de punteros.

p1=p1+9; p1 apunta al noveno elemento del tipo p1 que está más allá del elemento al

que apunta actualmente.

Punteros y arrays

char cad[80], *p1;

p1 = cad p1 ha sido asignado a la dirección del primer elemento del array

cad.

Para acceder al quinto elemento de cad se escribe:

cad[4] o *(p1+4)

Arrays de punteros

Array de punteros a enteros:

int *x [10];

92

Page 93: Programacion en Java

Para asignar la dirección de una variable entera llamada var al tercer elemento del array de punteros, se escribe:

x[2]=&var;

Para encontrar el valor de var:

*x[2]

Punteros a punteros

puntero -> variable Indirección simple

puntero -> puntero -> variable Indirección múltiple

float **balancenuevo;

balancenuevo no es un puntero a un número en coma flotante, sino un puntero a un puntero a float.

Ejemplo:

main(void)

{

int x, *p, **q;

x=10;

p=&x;

q=&p;

printf("%d",**q); /* imprime el valor de x */

return 0;

}

E/S por consola

getche ( ) lee un carácter del teclado, espera hasta que se pulse una tecla y entonces devuelve su valor. El eco de la tecla pulsada aparece automáticamente en la pantalla. Requiere el archivo de cabecera conio.h

putcahr ( ) imprime un carácter en la pantalla.

Los prototipos son:

int getche (void);

93

Page 94: Programacion en Java

int putchar (int c);

Ejemplo:

main ( ) /* cambio de mayúscula / minúscula */

{

char car;

do {

car=getche( );

if (islower(car)) putchar (toupper (car));

else putchar (tolower (car));

} while (car=!'.')

}

Hay dos variaciones de getche( ) :

• Getchar ( ): función de entrada de caracteres definida por el ANSI C. El problema es que guarda en un buffer la entrada hasta que se pulsa la tecla INTRO.

• Getch ( ): trabaja igual que getche( ) excepto que no muestra en la pantalla un eco del carácter introducido.

gets ( ) y puts ( )

Permiten leer y escribir cadenas de caracteres en la consola.

gets ( ) lee una cadena de caracteres introducida por el teclado y la sitúa en la dirección apuntada por su argumento de tipo puntero a carácter. Su prototipo es:

char * gets (char *cad);

Ejemplo:

main ( )

{

char cad[80];

gets (cad);

printf ("La longitud es %d", strlen (cad));

return 0;

94

Page 95: Programacion en Java

}

puts ( ) escribe su argumento de tipo cadena en la pantalla seguido de un carácter de salto de línea. Su prototipo es:

char * puts (const char *cad);

E/S por consola con formato

printf ( )

El prototipo de printf ( ) es:

int printf (const char *cad_fmt, ...);

La cadena de formato consiste en dos tipos de elementos: caracteres que se mostrarán en pantalla y órdenes de formato que empiezan con un signo de porcentaje y va seguido por el código del formato.

%c un único caracter

%d decimal

%i decimal

%e notación científica

%f decimal en coma flotante

%o octal

%s cadena de caracteres

%u decimales sin signo

%x hexadecimales

%% imprime un signo %

%p muestra un puntero

Las órdenes de formato pueden tener modificadores que especifiquen la longitud del campo, número de decimales y el ajuste a la izquierda.

Un entero situado entre % y el código de formato actúa como un especificador de longitud mínima de campo. Si se quiere rellenar con ceros, se pone un 0 antes del especificador de longitud de campo.

%05 rellena con ceros un número con menos de 5 dígitos.

%10.4f imprime un número de al menos diez caracteres con cuatro decimales.

95

Page 96: Programacion en Java

Si se aplica a cadenas o enteros el número que sigue al punto especifica la longitud máxima del campo.

%5.7s imprime una cadena de al menos cinco caracteres y no más de siete.

scanf ( )

Su prototipo es:

int scanf ( ) (const char *cadena_fmt, ...);

Ejemplo:

scanf ("%d",&cuenta);

96

Page 97: Programacion en Java

Curso de programación en Java

1. INTRODUCCIÓN A JAVA

1.1 Origen de Java

Sun Microsystems, líder en servidores para Internet, uno de cuyos lemas desde hace mucho tiempo es "the network is the computer" (lo que quiere dar a entender que el verdadero ordenador es la red en su conjunto y no cada máquina individual), es quien ha desarrollado el lenguaje Java, en un intento de resolver simultáneamente todos los problemas que se le plantean a los desarrolladores de software por la proliferación de arquitecturas incompatibles, tanto entre las diferentes máquinas como entre los diversos sistemas operativos y sistemas de ventanas que funcionaban sobre una misma máquina, añadiendo la dificultad de crear aplicaciones distribuidas en una red como Internet.

He podido leer más de cinco versiones distintas sobre el origen, concepción y desarrollo de Java, desde la que dice que este fue un proyecto que rebotó durante mucho tiempo por distintos departamentos de Sun sin que nadie le prestara ninguna atención, hasta que finalmente encontró su nicho de mercado en la aldea global que es Internet; hasta la más difundida, que justifica a Java como lenguaje de pequeños electrodomésticos.

Hace algunos años, Sun Microsystems decidió intentar introducirse en el mercado de la electrónica de consumo y desarrollar programas para pequeños dispositivos electrónicos. Tras unos comienzos dudosos, Sun decidió crear una filial, denominada FirstPerson Inc., para dar margen de maniobra al equipo responsable del proyecto.

El mercado inicialmente previsto para los programas de FirstPerson eran los equipos domésticos: microondas, tostadoras y, fundamentalmente, televisión interactiva. Este mercado, dada la falta de pericia de los usuarios para el manejo de estos dispositivos, requería unos interfaces mucho más cómodos e intuitivos que los sistemas de ventanas que proliferaban en el momento.

Otros requisitos importantes a tener en cuenta eran la fiabilidad del código y la facilidad de desarrollo. James Gosling, el miembro del equipo con más experiencia en lenguajes de programación, decidió que las ventajas aportadas por la eficiencia de C++ no compensaban el gran coste de pruebas y depuración. Gosling había estado trabajando en su tiempo libre en un lenguaje de programación que él había llamado Oak, el cual, aún partiendo de la sintaxis de C++, intentaba remediar las deficiencias que iba observando.

Los lenguajes al uso, como C o C++, deben ser compilados para un chip, y si se cambia el chip, todo el software debe compilarse de nuevo. Esto encarece mucho los desarrollos y el problema es especialmente acusado en el campo de la electrónica de consumo. La aparición de un chip más barato y, generalmente, más eficiente, conduce inmediatamente a los fabricantes a incluirlo en las nuevas series de sus cadenas de producción, por pequeña que sea la diferencia en precio ya que, multiplicada por la tirada masiva de los aparatos, supone un ahorro considerable. Por tanto, Gosling decidió mejorar las características de Oak y utilizarlo.

El primer proyecto en que se aplicó este lenguaje recibió el nombre de proyecto Green y consistía en un sistema de control completo de los aparatos electrónicos y el entorno de un hogar. Para ello se construyó un ordenador experimental denominado *7 (Star Seven). El sistema presentaba una interfaz basada en la representación de la casa de forma animada y el control se llevaba a cabo mediante una pantalla sensible al tacto. En el sistema aparecía Duke, la actual mascota de Java.

97

Page 98: Programacion en Java

Posteriormente se aplicó a otro proyecto denominado VOD (Video On Demand) en el que se empleaba como interfaz para la televisión interactiva. Ninguno de estos proyectos se convirtió nunca en un sistema comercial, pero fueron desarrollados enteramente en un Java primitivo y fueron como su bautismo de fuego.

Una vez que en Sun se dieron cuenta de que a corto plazo la televisión interactiva no iba a ser un gran éxito, urgieron a FirstPerson a desarrollar con rapidez nuevas estrategias que produjeran beneficios. No lo consiguieron y FirstPerson cerró en la primavera de 1994.

Pese a lo que parecía ya un olvido definitivo, Bill Joy, cofundador de Sun y uno de los desarrolladores principales del Unix de Berkeley, juzgó que Internet podría llegar a ser el campo de juego adecuado para disputar a Microsoft su primacía casi absoluta en el terreno del software, y vio en Oak el instrumento idóneo para llevar a cabo estos planes. Tras un cambio de nombre y modificaciones de diseño, el lenguaje Java fue presentado en sociedad en agosto de 1995.

Lo mejor será hacer caso omiso de las historias que pretenden dar carta de naturaleza a la clarividencia industrial de sus protagonistas; porque la cuestión es si independientemente de su origen y entorno comercial, Java ofrece soluciones a nuestras expectativas. Porque tampoco vamos a desechar la penicilina aunque haya sido su origen fruto de la casualidad.

1.2 Características de Java

Las características principales que nos ofrece Java respecto a cualquier otro lenguaje de programación, son:

Simple

Java ofrece toda la funcionalidad de un lenguaje potente, pero sin las características menos usadas y más confusas de éstos. C++ es un lenguaje que adolece de falta de seguridad, pero C y C++ son lenguajes más difundidos, por ello Java se diseñó para ser parecido a C++ y así facilitar un rápido y fácil aprendizaje.

Java elimina muchas de las características de otros lenguajes como C++, para mantener reducidas las especificaciones del lenguaje y añadir características muy útiles como el garbage collector (reciclador de memoria dinámica). No es necesario preocuparse de liberar memoria, el reciclador se encarga de ello y como es un thread de baja prioridad, cuando entra en acción, permite liberar bloques de memoria muy grandes, lo que reduce la fragmentación de la memoria.

Java reduce en un 50% los errores más comunes de programación con lenguajes como C y C++ al eliminar muchas de las características de éstos, entre las que destacan:

aritmética de punteros

no existen referencias

registros (struct)

definición de tipos (typedef)

macros (#define)

necesidad de liberar memoria (free)

98

Page 99: Programacion en Java

Aunque, en realidad, lo que hace es eliminar las palabras reservadas (struct, typedef), ya que las clases son algo parecido.

Además, el intérprete completo de Java que hay en este momento es muy pequeño, solamente ocupa 215 Kb de RAM.

Orientado a objetos

Java implementa la tecnología básica de C++ con algunas mejoras y elimina algunas cosas para mantener el objetivo de la simplicidad del lenguaje. Java trabaja con sus datos como objetos y con interfaces a esos objetos. Soporta las tres características propias del paradigma de la orientación a objetos: encapsulación, herencia y polimorfismo. Las plantillas de objetos son llamadas, como en C++, clases y sus copias, instancias. Estas instancias, como en C++, necesitan ser construidas y destruidas en espacios de memoria.

Java incorpora funcionalidades inexistentes en C++ como por ejemplo, la resolución dinámica de métodos. Esta característica deriva del lenguaje Objective C, propietario del sistema operativo Next. En C++ se suele trabajar con librerías dinámicas (DLLs) que obligan a recompilar la aplicación cuando se retocan las funciones que se encuentran en su interior. Este inconveniente es resuelto por Java mediante una interfaz específica llamada RTTI (RunTime Type Identification) que define la interacción entre objetos excluyendo variables de instancias o implementación de métodos. Las clases en Java tienen una representación en el runtime que permite a los programadores interrogar por el tipo de clase y enlazar dinámicamente la clase con el resultado de la búsqueda.

Distribuido

Java se ha construido con extensas capacidades de interconexión TCP/IP. Existen librerías de rutinas para acceder e interactuar con protocolos como http y ftp. Esto permite a los programadores acceder a la información a través de la red con tanta facilidad como a los ficheros locales.

La verdad es que Java en sí no es distribuido, sino que proporciona las librerías y herramientas para que los programas puedan ser distribuidos, es decir, que se corran en varias máquinas, interactuando.

Robusto

Java realiza verificaciones en busca de problemas tanto en tiempo de compilación como en tiempo de ejecución. La comprobación de tipos en Java ayuda a detectar errores, lo antes posible, en el ciclo de desarrollo. Java obliga a la declaración explícita de métodos, reduciendo así las posibilidades de error. Maneja la memoria para eliminar las preocupaciones por parte del programador de la liberación o corrupción de memoria.

También implementa los arrays auténticos, en vez de listas enlazadas de punteros, con comprobación de límites, para evitar la posibilidad de sobreescribir o corromper memoria resultado de punteros que señalan a zonas equivocadas. Estas características reducen drásticamente el tiempo de desarrollo de aplicaciones en Java.

Además, para asegurar el funcionamiento de la aplicación, realiza una verificación de los byte-codes, que son el resultado de la compilación de un programa Java. Es un código de máquina virtual que es interpretado por el intérprete Java. No es el código máquina directamente entendible por el hardware, pero ya ha pasado todas las fases del compilador: análisis de instrucciones, orden de operadores, etc., y ya tiene generada la pila de ejecución de órdenes.

99

Page 100: Programacion en Java

Java proporciona, pues:

Comprobación de punteros

Comprobación de límites de arrays

Excepciones

Verificación de byte-codes

Arquitectura neutral

Para establecer Java como parte integral de la red, el compilador Java compila su código a un fichero objeto de formato independiente de la arquitectura de la máquina en que se ejecutará. Cualquier máquina que tenga el sistema de ejecución (run-time) puede ejecutar ese código objeto, sin importar en modo alguno la máquina en que ha sido generado. Actualmente existen sistemas run-time para Solaris 2.x, SunOs 4.1.x, Windows 95, Windows NT, Linux, Irix, Aix, Mac, Apple y probablemente haya grupos de desarrollo trabajando en el porting a otras plataformas.

El código fuente Java se "compila" a un código de bytes de alto nivel independiente de la máquina. Este código (byte-codes) está diseñado para ejecutarse en una máquina hipotética que es implementada por un sistema run-time, que sí es dependiente de la máquina.

En una representación en que tuviésemos que indicar todos los elementos que forman parte de la arquitectura de Java sobre una plataforma genérica, obtendríamos una figura como la siguiente:

100

Page 101: Programacion en Java

En ella podemos ver que lo verdaderamente dependiente del sistema es la Máquina Virtual Java (JVM) y las librerías fundamentales, que también nos permitirían acceder directamente al hardware de la máquina. Además, habrá APIs de Java que también entren en contacto directo con el hardware y serán dependientes de la máquina, como ejemplo de este tipo de APIs podemos citar:

Java 2D: gráficos 2D y manipulación de imágenes

Java Media Framework : Elementos críticos en el tiempo: audio, video...

Java Animation: Animación de objetos en 2D

Java Telephony: Integración con telefonía

Java Share: Interacción entre aplicaciones multiusuario

Java 3D: Gráficos 3D y su manipulación

Seguro

La seguridad en Java tiene dos facetas. En el lenguaje, características como los punteros o el casting implícito que hacen los compiladores de C y C++ se eliminan para prevenir el acceso ilegal a la memoria. Cuando se usa Java para crear un navegador, se combinan las características del lenguaje con protecciones de sentido común aplicadas al propio navegador.

El lenguaje C, por ejemplo, tiene lagunas de seguridad importantes, como son los errores de alineación. Los programadores de C utilizan punteros en conjunción con operaciones aritméticas. Esto le permite al programador que un puntero referencie a un lugar conocido de la memoria y pueda sumar (o restar) algún valor, para referirse a otro lugar de la memoria. Si otros programadores conocen nuestras estructuras de datos pueden extraer información confidencial de nuestro sistema. Con un lenguaje como C, se pueden tomar números enteros aleatorios y convertirlos en punteros para luego acceder a la memoria:

101

Page 102: Programacion en Java

printf( "Escribe un valor entero: " );

scanf( "%u",&puntero );

printf( "Cadena de memoria: %s\n",puntero );

Otra laguna de seguridad u otro tipo de ataque, es el Caballo de Troya. Se presenta un programa como una utilidad, resultando tener una funcionalidad destructiva. Por ejemplo, en UNIX se visualiza el contenido de un directorio con el comando ls. Si un programador deja un comando destructivo bajo esta referencia, se puede correr el riesgo de ejecutar código malicioso, aunque el comando siga haciendo la funcionalidad que se le supone, después de lanzar su carga destructiva.

Por ejemplo, después de que el caballo de Troya haya enviado por correo el /etc/shadow a su creador, ejecuta la funcionalidad de ls persentando el contenido del directorio. Se notará un retardo, pero nada inusual.

El código Java pasa muchos tests antes de ejecutarse en una máquina. El código se pasa a través de un verificador de byte-codes que comprueba el formato de los fragmentos de código y aplica un probador de teoremas para detectar fragmentos de código ilegal -código que falsea punteros, viola derechos de acceso sobre objetos o intenta cambiar el tipo o clase de un objeto-.

Si los byte-codes pasan la verificación sin generar ningún mensaje de error, entonces sabemos que:

• El código no produce desbordamiento de operandos en la pila • El tipo de los parámetros de todos los códigos de operación son conocidos y correctos. • No ha ocurrido ninguna conversión ilegal de datos, tal como convertir enteros en punteros. • El acceso a los campos de un objeto se sabe que es legal: public, private, protected. • No hay ningún intento de violar las reglas de acceso y seguridad establecidas

El Cargador de Clases también ayuda a Java a mantener su seguridad, separando el espacio de nombres del sistema de ficheros local, del de los recursos procedentes de la red. Esto limita cualquier aplicación del tipo Caballo de Troya, ya que las clases se buscan primero entre las locales y luego entre las procedentes del exterior.

Las clases importadas de la red se almacenan en un espacio de nombres privado, asociado con el origen. Cuando una clase del espacio de nombres privado accede a otra clase, primero se busca en las clases predefinidas (del sistema local) y luego en el espacio de nombres de la clase que hace la referencia. Esto imposibilita que una clase suplante a una predefinida.

En resumen, las aplicaciones de Java resultan extremadamente seguras, ya que no acceden a zonas delicadas de memoria o de sistema, con lo cual evitan la interacción de ciertos virus. Java no posee una semántica específica para modificar la pila de programa, la memoria libre o utilizar objetos y métodos de un programa sin los privilegios del kernel del sistema operativo. Además, para evitar modificaciones por parte de los crackers de la red, implementa un método ultraseguro de autentificación por clave pública. El Cargador de Clases puede verificar una firma digital antes de realizar una instancia de un objeto. Por tanto, ningún objeto se crea y almacena en memoria, sin que se validen los privilegios de acceso. Es decir, la seguridad se integra en el momento de compilación, con el nivel de detalle y de privilegio que sea necesario.

Dada, pues la concepción del lenguaje y si todos los elementos se mantienen dentro del estándar marcado por Sun, no hay peligro. Java imposibilita, también, abrir ningún fichero de la máquina local (siempre que se realizan operaciones con archivos, éstas trabajan sobre el disco duro de la

102

Page 103: Programacion en Java

máquina de donde partió el applet), no permite ejecutar ninguna aplicación nativa de una plataforma e impide

que se utilicen otros ordenadores como puente, es decir, nadie puede utilizar nuestra máquina para hacer peticiones o realizar operaciones con otra. Además, los intérpretes que incorporan los navegadores de la Web son aún más restrictivos. Bajo estas condiciones (y dentro de la filosofía de que el único ordenador seguro es el que está apagado, desenchufado, dentro de una cámara acorazada en un bunker y rodeado por mil soldados de los cuerpos especiales del ejército), se puede considerar que Java es un lenguaje seguro y que los applets están libres de virus.

Respecto a la seguridad del código fuente, no ya del lenguaje, JDK proporciona un desemsamblador de byte-code, que permite que cualquier programa pueda ser convertido a código fuente, lo que para el programador significa una vulnerabilidad total a su código. Utilizando javap no se obtiene el código fuente original, pero sí desmonta el programa mostrando el algoritmo que se utiliza, que es lo realmente interesante. La protección de los programadores ante esto es utilizar llamadas a programas nativos, externos (incluso en C o C++) de forma que no sea descompilable todo el código; aunque así se pierda portabilidad. Esta es otra de las cuestiones que Java tiene pendientes.

Portable

Más allá de la portabilidad básica por ser de arquitectura independiente, Java implementa otros estándares de portabilidad para facilitar el desarrollo. Los enteros son siempre enteros y además, enteros de 32 bits en complemento a 2. Además, Java construye sus interfaces de usuario a través de un sistema abstracto de ventanas de forma que las ventanas puedan ser implantadas en entornos Unix, Pc o Mac.

Interpretado

El intérprete Java (sistema run-time) puede ejecutar directamente el código objeto. Enlazar (linkar) un programa, normalmente, consume menos recursos que compilarlo, por lo que los desarrolladores con Java pasarán más tiempo desarrollando y menos esperando por el ordenador. No obstante, el compilador actual del JDK es bastante lento. Por ahora, que todavía no hay compiladores específicos de Java para las diversas plataformas, Java es más lento que otros lenguajes de programación, como C++, ya que debe ser interpretado y no ejecutado como sucede en cualquier programa tradicional.

Se dice que Java es de 10 a 30 veces más lento que C, y que tampoco existen en Java proyectos de gran envergadura como en otros lenguajes. La verdad es que ya hay comparaciones ventajosas entre Java y el resto de los lenguajes de programación, y una ingente cantidad de folletos electrónicos que supuran fanatismo en favor y en contra de los distintos lenguajes contendientes con Java. Lo que se suele dejar de lado en todo esto, es que primero habría que decidir hasta que punto Java, un lenguaje en pleno desarrollo y todavía sin definición definitiva, está maduro como lenguaje de programación para ser comparado con otros; como por ejemplo con Smalltalk, que lleva más de 20 años en cancha.

La verdad es que Java para conseguir ser un lenguaje independiente del sistema operativo y del procesador que incorpore la máquina utilizada, es tanto interpretado como compilado. Y esto no es ningún contrasentido, me explico, el código fuente escrito con cualquier editor se compila generando el byte-code. Este código intermedio es de muy bajo nivel, pero sin alcanzar las instrucciones máquina propias de cada plataforma y no tiene nada que ver con el p-code de Visual Basic. El byte-code corresponde al 80% de las instrucciones de la aplicación. Ese mismo código es el que se puede ejecutar sobre cualquier plataforma. Para ello hace falta el run-time, que sí es completamente dependiente de la máquina y del sistema operativo, que interpreta dinámicamente

103

Page 104: Programacion en Java

el byte-code y añade el 20% de instrucciones que faltaban para su ejecución. Con este sistema es fácil crear aplicaciones multiplataforma, pero para ejecutarlas es necesario que exista el run-time correspondiente al sistema operativo utilizado.

Multithreaded

Al ser multithreaded (multihilvanado, en mala traducción), Java permite muchas actividades simultáneas en un programa. Los threads (a veces llamados, procesos ligeros), son básicamente pequeños procesos o piezas independientes de un gran proceso. Al estar los threads contruidos en el lenguaje, son más fáciles de usar y más robustos que sus homólogos en C o C++.

El beneficio de ser miltithreaded consiste en un mejor rendimiento interactivo y mejor comportamiento en tiempo real. Aunque el comportamiento en tiempo real está limitado a las capacidades del sistema operativo subyacente (Unix, Windows, etc.), aún supera a los entornos de flujo único de programa (single-threaded) tanto en facilidad de desarrollo como en rendimiento.

Cualquiera que haya utilizado la tecnología de navegación concurrente, sabe lo frustrante que puede ser esperar por una gran imagen que se está trayendo. En Java, las imágenes se pueden ir trayendo en un thread independiente, permitiendo que el usuario pueda acceder a la información en la página sin tener que esperar por el navegador.

Dinamico

Java se beneficia todo lo posible de la tecnología orientada a objetos. Java no intenta conectar todos los módulos que comprenden una aplicación hasta el tiempo de ejecución. Las librería nuevas o actualizadas no paralizarán las aplicaciones actuales (siempre que mantengan el API anterior).

Java también simplifica el uso de protocolos nuevos o actualizados. Si su sistema ejecuta una aplicación Java sobre la red y encuentra una pieza de la aplicación que no sabe manejar, tal como se ha explicado en párrafos anteriores, Java es capaz de traer automáticamente cualquiera de esas piezas que el sistema necesita para funcionar.

Java, para evitar que los módulos de byte-codes o los objetos o nuevas clases, haya que estar trayéndolos de la red cada vez que se necesiten, implementa las opciones de persistencia, para que no se eliminen cuando de limpie la caché de la máquina.

¿Cuál es la ventaja de todo esto?¿Qué gano con Java?

104

Page 105: Programacion en Java

• Primero: No debes volver a escribir el código si quieres ejecutar el programa en otra máquina. Un solo código funciona para todos los browsers compatibles con Java o donde se tenga una Máquina Virtual de Java (Mac's, PC's, Sun's, etc).

• Segundo: Java es un lenguaje de programación orientado a objetos, y tiene todos los beneficios que ofrece esta metodología de programacion (más adelante doy una pequeña introducción a la filosofía de objetos).

• Tercero: Un browser compatible con Java deberá ejecutar cualquier programa hecho en Java, esto ahorra a los usuarios tener que estar insertando "plug-ins" y demás programas que a veces nos quitan tiempo y espacio en disco.

• Cuarto: Java es un lenguaje y por lo tanto puede hacer todas las cosas que puede hacer un lenguaje de programación: Cálculos matemáticos, procesadores de palabras, bases de datos, aplicaciones gráficas, animaciones, sonido, hojas de cálculo, etc.

• Quinto: Si lo que me interesa son las páginas de Web, ya no tienen que ser estáticas, se le pueden poner toda clase de elementos multimedia y permiten un alto nivel de interactividad, sin tener que gastar en paquetes carísimos de multimedia.

Todo esto suena muy bonito pero tambien se tienen algunas limitantes:

• La velocidad. • Los programas hechos en Java no tienden a ser muy rápidos, supuestamente se está

trabajando en mejorar esto.Como los programas de Java son interpretados nunca alcanzan la velocidad de un verdadero ejecutable.

• Java es un lenguaje de programación. Esta es otra gran limitante, por más que digan que es orientado a objetos y que es muy fácil de aprender sigue siendo un lenguaje y por lo tanto aprenderlo no es cosa fácil. Especialmente para los no programadores.

• Java es nuevo. En pocas palabras todavía no se conocen bien todas sus capacidades.

Pero en general Java posee muchas ventajas y se pueden hacer cosas muy interesantes con esto. Hay que prestar especial atención a lo que está sucediendo en el mundo de la computación, a pesar de que Java es relativamente nuevo, posee mucha fuerza y es tema de moda en cualquier medio computacional. Muchas personas apuestan a futuro y piensan en Java. La pregunta es : ¿Estarán en lo correcto? La verdad es que no se, pero este manual no es para filosofar sobre el futuro del lenguaje sino para aprender a programarlo.

1.3 HotJava

HotJava, en pocas palabras, es un navegador con soporte Java (Java-enabled), desarrollado en Java. Como cualquier navegador de Web, HotJava puede decodificar HTML estándar y URLs estándares, aunque no soporta completamente el estándar HTML 3.0. La ventaja sobre el resto de navegadores, sin soporte Java, es que puede ejecutar programas Java sobre la red. La diferencia con Netscape, es que tiene implementado completamente los sistemas de seguridad que propone Java, esto significa que puede escribir y leer en el disco local, aunque esto hace disminuir la seguridad, ya que se pueden grabar en nuestro disco programas que contengan código malicioso e introducirnos un virus, por ejemplo. No obstante, el utilizar esta característica de HotJava es decisión del usuario.

1.4 Java para aplicaciones corporativas

Java actualmente está en boca de todos, Java e Intranet son las palabras de moda. Pero, surge la pregunta de si esta es una buena tecnología para desarrollar aplicaciones corporativas. Y la respuesta es afirmativa y voy a proponer argumentos para esa afirmación. En donde la red sea algo crítico, Java facilita tremendamente la vida de la programación corporativa.

105

Page 106: Programacion en Java

Durante años, las grandes empresas se han convencido de que la "red" corporativa es la arteria por donde fluye la sangre que mantiene vivo su negocio. Desde el gran servidor de sus oficinas centrales, hasta los servidores de las delegaciones, las estaciones de trabajo de los programadores y la marabunta de PCs, la información va fluyendo de unos a otros. Para muchas compañías, la Red es la Empresa.

Si esta red no se mantiene sana, los pedidos no llegan, el inventario no se actualiza, el software no se desarrolla adecuadamente, los clientes no están satisfechos y, fundamentalmente, el dinero no entra. La necesidad de diagnosticar y reducir la arterioesclerosis de la red, hace que se estén inyectando continuamente nuevas metodologías que subsanen este grave problema.

¿Es Java la medicina? Está claro que cuando vemos un cepillo animado limpiando los dientes, cubos moviéndose en 3-D, o una banda de gatos locos en applets de Java, nos convencemos de que es el lenguaje idóneo para Internet. Pero, qué pasa con las aplicaciones corporativas, ¿sería una buena tecnología allí donde la red es el punto crítico? Vamos a intentar responder comparando las capacidades de Java contra la lista de necesidades de la red corporativa.

Desarrollo rápido de aplicaciones

Hace años, se decía que los programadores pronto desaparecerían. Los generadores automáticos de programas, eliminarían a los generadores humanos y el mundo sería un lugar mejor para vivir. Desafortunadamente, quienes decían esto no tuvieron en cuenta una acelerada demanda de software de calidad para muy diferentes aplicaciones. Sin embargo, la tecnología de objetos pronto vino a intentar facilitar la tarea, adoptando el modelo de "generar parte de un programa", así, generando la parte básica de un programa (los objetos), se podría conectar con otras partes para proporcionar diferentes utilidades al usuario.

El lenguaje C++ es una buena herramienta, pero no cumple totalmente la premisa. Visual Basic y NextStep, se acercan cada vez más al poder de los objetos. Java facilita la creación de entornos de desarrollo-aplicaciones de modo similar, pero además es flexible, poderoso y efectivo. Los programadores ahora disponen de herramientas de programación de calidad beta, que apuntan hacia esa meta, como son el Java WorkShop de SunSoft, el entorno Java de Borland, el Café de Symantec, y pronto, herramientas más sofisticadas como Netcode o FutureTense. Esto proporciona una gran progresión a los entornos de desarrollo Java.

Aplicaciones efectivas y eficientes

Las aplicaciones que se crean en grandes empresas deben ser más efectivas que eficientes; es decir, conseguir que el programa funcione y el trabajo salga adelante es más importante que el que lo haga eficientemente. Esto no es una crítica, es una realidad de la programación corporativa. Al ser un lenguaje más simple que cualquiera de los que ahora están en el cajón de los programadores, Java permite a éstos concentrarse en la mecánica de la aplicación, en vez de pasarse horas y horas incorporando APIs para el control de las ventanas, controlando minuciosamente la memoria, sincronizando los ficheros de cabecera y corrigiendo los agónicos mensajes del linker. Java tiene su propio toolkit para interfaces, maneja por sí mismo la memoria que utilice la aplicación, no permite ficheros de cabecera separados (en aplicaciones puramente Java) y solamente usa enlace dinámico.

Muchas de las implementaciones de Java actuales son puros intérpretes. Los byte-codes son interpretados por el sistema run-time de Java, la Máquina Virtual Java (JVM), sobre el ordenador del usuario. Aunque ya hay ciertos proveedores que ofrecen compiladores nativos Just-In-Time (JIT). Si la Máquina Virtual Java dispone de un compilador instalado, las secciones (clases) del byte-code de la aplicación se compilarán hacia la arquitectura nativa del ordenador del usuario.

106

Page 107: Programacion en Java

Los programas Java en ese momento rivalizarán con el rendimiento de programas en C++. Los compiladores JIT no se utilizan en la forma tradicional de un compilador; los programadores no compilan y distribuyen binarios Java a los usuarios. La compilación JIT tiene lugar a partir del byte-code Java, en el sistema del usuario, como una parte (opcional) del entorno run-time local de Java.

Muchas veces, los programadores corporativos, ansiosos por exprimir al máximo la eficiencia de su aplicación, empiezan a hacerlo demasiado pronto en el ciclo de vida de la aplicación. Java permite algunas técnicas innovadoras de optimización. Por ejemplo, Java es inherentemente multithreaded, a la vez que ofrece posibilidades de multithread como la clase Thread y mecanismos muy sencillos de usar de sincronización; Java en sí utiliza threads. Los desarrolladores de compiladores inteligentes pueden utilizar esta característica de Java para lanzar un thread que compruebe la forma en que se está utilizando la aplicación. Más específicamente, este thread podría detectar qué métodos de una clase se están usando con más frecuencia e invocar a sucesivos niveles de optimización en tiempo de ejecución de la aplicación. Cuanto más tiempo esté corriendo la aplicación o el applet, los métodos estarán cada vez más optimizados (Guava de Softway es de este tipo).

Si un compilador JIT está embebido en el entorno run-time de Java, el programador no se preocupa de hacer que la aplicación se ejecute óptimamente. Siempre he pensado que en los Sistemas Operativos tendría que aplicarse esta filosofía; un optimizador progresivo es un paso más hacia esta idea.

Portabilidad para programador y programa

En una empresa de relativo tamaño hay una pléyade diferente de ordenadores. Probablemente nos encontremos con estaciones de trabajo Sun para el desarrollo de software, hordas de PCs para cada empleado, algún Mac en el departamento de documentación, una estación de trabajo HP en administración y una estación SGI en la sala de demos. Desarrollar aplicaciones corporativas para un grupo tan diferente de plataformas en excesivamente complejo y caro. Hasta ahora era complicado convencer a los programadores de cada arquitectura que utilizasen un API común para reducir el coste de las aplicaciones.

Con un entorno run-time de Java portado a cada una de las arquitecturas de las plataformas presentes en la empresa y una buena librería de clases ("packages" en Java), los programadores pueden entenderse y encontrar muy interesante trabajar con Java. Esta posibilidad hará tender a los programadores hacia Java, justo donde otros intentos anteriores con entornos universales (como Galaxy o XVT) han fracasado. Estos APIs eran simplemente inadecuados, no orientados a redes y, verdaderamente, pesados.

Una vez que los programas estén escritos en Java, otro lado interesante del asunto es que los programadores también son portables. El grupo de programadores de la empresa puede ahora enfrentarse a un desarrollo para cualquiera de las plataformas. La parte del cliente y del servidor de una aplicación estarán ahora escritas en el mismo lenguaje. Ya no será necesario tener un grupo que desarrolle en Solaris en del departamento de I+D, programadores trabajando sobre Visual Basic en el departamento de documentación y programadores sobre GNU en proyectos especiales; ahora todos ellos podrán estar juntos y formar el grupo de software de la empresa.

Costes de desarrollo

En contraste con el alto coste de los desarrollos realizados sobre estaciones de trabajo, el coste de creación de una aplicación Java es similar al de desarrollar sobre un PC.

Desarrollar utilizando un software caro para una estación de trabajo (ahora barata) es un problema en muchas empresas. La eficiencia del hardware y el poco coste de mantenimiento de una

107

Page 108: Programacion en Java

estación de trabajo Sun, por ejemplo, resulta muy atractivo para las empresas; pero el coste adicional del entorno de desarrollo con C++ es prohibitivo para la gran mayoría de ellas. La llegada de Java e Intranet reducen considerablemente estos costes. Las herramientas Java ya no están en el entorno de precios de millones de pesetas, sino a los niveles confortables de precio de las herramientas de PCs. Y con el crecimiento cada día mayor de la comunidad de desarrolladores de software freeware y shareware que incluso proporcionan el código fuente, los programadores corporativos tienen un amplio campo donde moverse y muchas oportunidades de aprender y muchos recursos a su disposición.

El éxito que Internet ha proporcionado a los equipos de software corporativos es un regalo. El precio del software es ahora el mismo para un poderoso equipo corriendo Unix que para un PC. Incluso Netscape tiene al mismo precio la versión Unix de su servidor Web SuiteSpot que la versión PC/NT. Esta es la filosofía de precios que parece ser será la que se siga con las herramientas basadas en Java.

Mantenimiento y soporte

Un problema bien conocido que ocurre con el software corporativo es la demanda de cuidados y realimentación. Java no es, ciertamente, la cura para la enfermedad del mantenimiento, pero tiene varias características que harán la vida del enfermero más fácil.

Uno de los componentes del JDK es javadoc. Si se usan ciertas convenciones en el código fuente Java (como comenzar un comentario con /** y terminarlo con */), javadoc se puede fácilmente generar páginas HTML con el contenido de esos comentarios, que pueden visualizarse en cualquier navegador. La documentación del API de Java ha sido creada de este modo. Esto hace que el trabajo de documentar el código de nuevas clases Java sea trivial.

Otro gran problema del desarrollador corporativo es la creación y control de makefiles. Leerse un makefile es como estar leyendo la historia de empresa. Normalmente se pasan de programador a programador, quitando la información que no es esencial, siempre que se puede. Esto hace que muchos de los makefiles de las aplicaciones contengan docenas de librerías, una miríada de ficheros de cabecera y ultra-confusos macros. Es como mirar en el estómago de la ballena de Jonás.

Java reduce las dependencia de complejos makefiles drásticamente. Primero, no hay ficheros de cabecera. Java necesita que todo el código fuente de una clase se encuentre en un solo fichero. Java tiene la inteligencia de make en el propio lenguaje para simplificar la compilación de byte-codes.

Por ejemplo:

public class pepe { // Fichero: pepe.java

Guitarra flamenca ;

}

public class guitarra { // Fichero: guitarra.java

}

% javac -verbose pepe.java

108

Page 109: Programacion en Java

[parsed pepe.java in 720ms]

[loaded C:\JAVA\BIN\..\classes\java\lang\Object.class in 220ms]

[checking class pepe]

[parsed .\\Guitarra.java in 50ms]

[wrote pepe.class]

[checking class Guitarra]

[wrote .\\Guitarra.class]

[done in 2300ms]

El compilador Java se da cuenta de que necesita compilar el fichero guitarra.java. Ahora vamos a forzarlo a que recompile pepe.java sin cambiar guitarra.java, podremos comprobar que el compilador de byte-code Java no recompila innecesariamente el fichero guitarra.java.

% javac -verbose pepe.java

[parsed pepe.java in 440ms]

[loaded C:\JAVA\BIN\..\classes\java\lang\Object.class in 160ms]

[checking class pepe]

[loaded .\\Guitarra.java in 0ms]

[wrote pepe.class]

[done in 1860ms]

Ahora, si modificamos guitarra.java (añadiendo, por ejemplo, otro miembro a la clase) y compilamos pepe.java, el compilador Java se dará cuenta de que debe recompilar tanto pepe.java como guitarra.java

% javac -verbose pepe.java

[parsed pepe.java in 710ms]

[loaded C:\JAVA\BIN\..\classes\java\lang\Object.class in 220ms]

[checking class pepe]

[parsed .\\Guitarra.java in 0ms]

[wrote pepe.class]

[checking class Guitarra]

109

Page 110: Programacion en Java

[wrote .\\Guitarra.class]

[done in 2640ms]

En el libro Just Java de Peter van der Linden hay un capítulo excelente acerca del compilador de Java, si tienes oportunidad, no dejes de leerlo.

Aprendizaje

Si la empresa está llena de programadores de C++ con alguna experiencia en el manejo de librería gráficas, aprenderán rápidamente lo esencial de Java. Si el equipo de ingenieros no conoce C++, pero maneja cualquier otro lenguaje de programación orientada a objetos, les llevará pocas semanas dominar la base de Java. Lo que sí que no es cierto es que haya que aprender C++ antes de aprender Java.

Si los ingenieros de la empresa no conocen ningún lenguaje orientado a objetos, sí que tienen que aprender los fundamentos de esta tecnología antes de nada, y luego aplicarlos a la programación con Java. El análisis y diseño orientado a objetos debe ser comprendido antes de intentar nada con Java. Los programadores de Java sin un fondo de conocimientos de OOA/D producirán código pobre. Además, los libros sobre Java crecen como la espuma, ya hay más de 25 publicados, y si buscas "Progamming in Java" en la Red, encontrarás 312 Web sites, y 30 más dedicados a "Learning Java". Y si esto, evidentemente, no es el sustituto de un instructor humano, hay ya varias empresas que ofrecen enseñanza de Java, entre ellas, Sun.

2. INSTALACIÓN DEL JDK

Actualmente ya hay entornos de desarrollo integrados completos para Java, diferentes del JDK de Sun. Symantec dispone de un compilador de Java para Windows 95 y Windows NT, con las ventajas del aumento de velocidad de proceso y capacidades multimedia que esto proporciona, Symantec Café. Borland también está trabajando en ello y la nueva versión de su entorno de desarrollo soporta Java. Sun ha lanzado la versión comercial de su propio entorno de desarrollo para Java, el Java Workshop, enteramente escrito en Java. Y Microsoft ha puesto en el mercado Visual J++, que sigue el estilo de todas sus herramientas de desarrollo.

No obstante, trataremos solamente el JDK, que hasta el momento es lo más conocido. El entorno básico del JDK de Java que proporciona Sun está formado por herramientas en modo texto, que son: java, intérprete que ejecuta programas en byte-code. javac, compilador de Java que convierte el código fuente en byte-code. javah, crea ficheros de cabecera para implementar métodos para cualquier clase. javap, es un descompilador de byte-code a código fuente Java. javadoc, es un generador automático de documentos HTML a partir del código fuente Java. javaprof, es un profiler para aplicaciones de un solo thread. HotJava, es un navegador Web escrito completamente en Java.

El entorno habitual pues, consiste en un navegador que pueda ejecutar applets, un compilador que convierta el código fuente Java a byte-code y el intérprete Java para ejecutar los programas. Estos son los componenetes básicos para desarrollar algo en Java. No obstante se necesita un editor para escribir el código fuente, y no son estrictamente necesarias otras herramientas como el debugger, un entorno visual, la documentación o un visualizador de jerarquía de clases. Tan es así, que disponiendo del navegador Netscape 2.0 no se necesita ni tan siquiera el JDK (a petición de varios amigos que disfrutan del uso de Linux pero no disponen de soporte ELF para poder utilizar el JDK portado por Randy Chapman, les indicaré como conseguir utilizar el compilador embebido en Netscape).

2.1 Windows

110

Page 111: Programacion en Java

La versión del JDK para Windows es un archivo autoextraible. Se necesitan alrededor de 6 Mb de espacio libre en disco. Ejecutar el fichero, que desempaquetará el contenido del archivo. El directorio donde se instale no es importante, pero supondremos que se instala en el raiz del disco C:, en cuyo caso los archivos colgarán de c:\java. Es necesario añadir c:\java\bin a la variable de entorno PATH.

Además de los ficheros java, el JDK incluye dos librerías dinámicas, MSVCRT20.DLL y MFC30.DLL, que se instalarán en el directorio de Java. Si tienes ninguna copia de estos ficheros en tu ordenador (probablemente en el directorio system de Windows) copia estos ficheros en el directorio c:\java\bin. Si estos ficheros ya están en tu ordenador, elimina las copias extra que instala el JDK.

2.2 Solaris

La versión del JDK para Solaris es un fichero tar comprimido. Se necesitan alrededor de 9 Mb de disco para descomprimir el JDK, aunque el doble de espacio sería una cifra más cómoda. Ejecutar los siguientes comandos:

% uncompress JDK-beta-solaris2-sparc.tar.Z

% tar xvf JDK-beta-solaris2-sparc-tar

Puedes descomprimir el archivo en tu directorio home, o, si tienes privilegios de supervisor, en algún sitio más conveniente de /usr/local para que todos los usuarios tengan acceso a los ficheros. Sin embargo, los privilegios del supervisor no son necesarios para instalar y ejecutar Java. Por simplicidad, supondré que has descomprimido el JDK en /usr/local, aunque el path completo donde se haga no tiene relevancia (también es posible colocarlo en /opt que es donde residen todas las aplicaciones de Solaris). Si lo has colocado en un sitio diferente, simplemente sustituye /usr/local por ese directorio (si lo has descomprimido en tu home, puedes utilizar ~/java y ~/hotjava, en vez del path completo).

Es necesario añadir /usr/local/java/bin a la variable de entorno PATH. Utiliza el siguiente comando (suponiendo que tengas el shell csh o tcsh):

set path=($PATH /usr/local/java/bin)

También puedes añadir esta línea al final del fichero .profile y .cshrc, y ya tienes el sistema listo para ejecutar applets. Si quieres desembarazarte de la ventana que aparece cada vez que lances el appletviewer con la licencia de Sun, crea un directorio que se llame .hotjava en el directorio java/bin y ya no volverás a verla.

2.3 Linux

Necesitas un kernel que soporte binarios ELF, por lo tanto tu Linux debe ser la versión 1.2.13 o superior, las anteriores tienen un bug que hacen que javac no funcione. Necesitas también Netscape, versión 2.0b4 o posterior. Sobre la versión 1.2.13 del kernel de Linux, hay que seguir los pasos que indico para conseguir que JDK funcione:

• Bajarse el JDK, linux.jdk-1.0-try4.static-motif.tar.gz y

linux.jdk-1.0 try1.common.tar.gz a

/usr/local, descomprimirlo y hacer 'tar xvf'

111

Page 112: Programacion en Java

• En el fichero .java_wrapper (si no existe, crearlo) cambiar las variable J_HOME y PRG, para que queden como:

J_HOME=/usr/local/java

PRG=/usr/local/java/bin

• Bajarse la librería libc.5.2.18.bin.tar.gz a /, descomprimirla, hacer 'tar xvf'.

• Asegurarse de que /lib/libc.so.5 es un link simbólico a este nuevo fichero.Si no lo es, hacer el /lib 'ln -s libc.so.5.2.18 libc.so.5'

• Bajarse ld-so.1.7.14.tar.gz a un directorio temporal, descomprimirlo y hacer 'tar xvf'.

• Ejecutar 'instldso.sh' y eliminar el directorio temporal.

• Añadir /usr/local/java a la variable de entorno PATH. Si se desea que esté fijada para todos los usuarios, incorporar el directorio a la varible PATH que se fija en el fichero /etc/profile.

• Bajarse netscape-v202-export.i486-unknown-linux.tar.z a usr/local/netscape, descomprimirlo y hacer 'tar xvf'

• Crear un link en /usr/local/bin a /usr/local/netscape/netscape

Esto debería ser suficiente para compilar cualquier cosa en Java/Linux. En caso de tener problemas, es el momento de recurrir a las FAQ.

Siguiendo los pasos indicados ya se puede ejecutar el ejemplo del Tic-Tac-Toe que propone la hoja de instalación que Sun ha incluido en todas sus versiones y que en Linux consistiría en cambiarse al directorio de la demo:

% cd /usr/local/java/demo/TicTacToe

ejecutar el visualizador de applets sobre la página html:

% appletviewer example1.html

y a jugar a las tres en raya. Por cierto, que el algoritmo que usa el ordenador está falseado por lo que es posible ganarle.

2.4 Compilación sin JDK

Parece raro, pero se puede conseguir. Lo único necesario es el navegador Netscape 2.0. Este navegador, junto con la máquina virtual Java (JVM) y el sistema run-time, tiene un compilador Java.

Si no se dispone del Java Development Kit (JDK), que no está disponible para todas las plataformas, pero sí de la versión de Netscape para nuestra plataforma, aquí van los pasos a seguir para utilizar el compilador de Java embebido en Netscape.

112

Page 113: Programacion en Java

Como necesito partir de algún punto para tomarlo como referencia, voy a suponer que estamos sobre Linux y que vamos a prescindir del JDK de Randy Chapman. Lo que habría que hacer sería lo siguiente.

• Primero. Instalar Netscape en el ordenador. Asegurarse de entender perfectamente y leerse hasta el final el fichero README, para seguir las instrucciones específicas de la instalación de Netscape en la plataforma y que Netscape funcione perfectamente. En nuestro caso, en que vamos a intentar compilar código Java con Netscape sobre Linux, la pieza clave es la situación del fichero moz2_0.zip, que en mi máquina está en /usr/local/netscape/java/classes.

• Segundo. Extraer de una copia cualquiera del JDK (aunque sea de otra plataforma), el fichero java/lib/classes.zip y guardarlo en el mismo sitio que el fichero moz2_0.zip; esta localización no es necesaria, pero simplifica la estructura.

• Tercero. Fijar la variable de entorno CLASSPATH para que Netscape pueda encontrar sus propias clases además de las clases del Java de Sun. Asegurarse de incluir el "directorio actual", para poder compilar a la vez que se usan los ficheros .zip de Netscape y Sun. Por ejemplo:

setenv CLASSPATH

.:/usr/local/netscape/java/classes/moz2_0.zip :

/usr/local/netscape/java/classes/classes.zip

• Cuarto. Compilar el código Java (applet o aplicación) con el comando: netscape -java sun.tools.javac.Main [fichero].java (sustituir el nombre del fichero con el código Java en vez de [fichero]). Esto convertirá el código fuente Java en byte-code, generándose el archivo [fichero].class.

• Quinto. Comprobar si se puede ejecutar la aplicación con el comando:

netscape -java [clase]

(sustituir el nombre de la clase de la aplicación -la que contiene la rutina main-

en vez de [clase]).

• Sexto. Si se ha compilado un applet Java, construir una página html que lo utilice para visualizarlo con el navegador en su forma normal. O también se puede visualizar utilizando el appletviewer, ejecutando:

netscape -java sun.applet.AppletViewer [clase]

Desgraciadamente, la sentencia anterior no parece funcionar en todos los

sistemas. Hay amigos míos que no han sido capaces de visualizar applets con

este método.

Para aprovechar el tiempo, se puede crear un script que recoja los pasos 3, 4 y 6. Si estamos utilizando el csh, el contenido del script sería:

113

Page 114: Programacion en Java

#/bin/csh -f setenv CLASSPATH

.:/usr/local/netscape/java/classes/moz2_0.zip:

/usr/local/netscape/java/classes/classes.zip

netscape -java sun.tools.javac.Main $1

y lo almacenaríamos como javac. Se ha de hacer el script ejecutable y cambiar /bin/csh por el path completo donde esté situado el csh. De forma semejante podemos definir el intérprete java y el appletviewer, sustituyendo la línea adecuada de llamada a Netscape.

3. CONCEPTOS BÁSICOS DE JAVA

3.1 Programación en Java

Cuando se programa en Java, se coloca todo el código en métodos, de la misma forma que se escriben funciones en lenguajes como C.

Comentarios

En Java hay tres tipos de comentarios:

// comentarios para una sola línea

/* comentarios de una o

más líneas

*/

/** comentario de documentación, de una o más líneas

*/

Los dos primeros tipos de comentarios son los que todo programador conoce y se utilizan del mismo modo. Los comentarios de documentación, colocados inmediatamente antes de una declaración (de variable o función), indican que ese comentario ha de ser colocado en la documentación que se genera automáticamente cuando se utiliza la herramienta de Java, javadoc. Dichos comentarios sirven como descripción del elemento declarado permitiendo generar una documentación de nuestras clases escrita al mismo tiempo que se genera el código.

En este tipo de comentario para documentación, se permite la introducción de algunos tokens o palabras clave, que harán que la información que les sigue aparezca de forma diferente al resto en la documentación.

Identificadores

Los identificadores nombran variables, funciones, clases y objetos; cualquier cosa que el programador necesite identificar o usar.

114

Page 115: Programacion en Java

En Java, un identificador comienza con una letra, un subrayado (_) o un símbolo de dólar ($). Los siguientes caracteres pueden ser letras o dígitos. Se distinguen las mayúsculas de las minúsculas y no hay longitud máxima.

Serían identificadores válidos:

identificador

nombre_usuario

Nombre_Usuario

_variable_del_sistema

$transaccion

y su uso sería, por ejemplo:

int contador_principal;

char _lista_de_ficheros;

float $cantidad_en_Ptas;

Palabras clave

Las siguientes son las palabras clave que están definidas en Java y que no se pueden utilizar como indentificadores:

abstract continue for new switch

boolean default goto null synchronized

break do if package this

byte double implements private threadsafe

byvalue else import protected throw

case extends instanceof public transient

catch false int return true

char final interface short try

class finally long static void

const float native super while

Palabras Reservadas

115

Page 116: Programacion en Java

Además, el lenguaje se reserva unas cuantas palabras más, pero que hasta ahora no tienen un cometido específico. Son:

cast future generic inner

operator outer rest var

Literales

Un valor constante en Java se crea utilizando una representación literal de él. Java utiliza cinco tipos de elementos: enteros, reales en coma flotante, booleanos, caracteres y cadenas, que se pueden poner en cualquier lugar del código fuente de Java. Cada uno de estos literales tiene un tipo correspondiente asociado con él.

Enteros:

byte 8 bits complemento a dos

short 16 bits complemento a dos

int 32 bits complemento a dos

long 64 bits complemento a dos

Por ejemplo: 21 077 0xDC00

Reales en coma flotante:

float 32 bits IEEE 754

double 64 bits IEEE 754

Por ejemplo: 3.14 2e12 3.1E12

Booleanos:

true

false

Caracteres:

Por ejemplo: a \t \u???? [????] es un número unicode

Cadenas:

Por ejemplo: "Esto es una cadena literal"

Arrays

Se pueden declarar en Java arrays de cualquier tipo:

116

Page 117: Programacion en Java

char s[];

int iArray[];

Incluso se pueden construir arrays de arrays:

int tabla[][] = new int[4][5];

Los límites de los arrays se comprueban en tiempo de ejecución para evitar desbordamientos y la corrupción de memoria.

En Java un array es realmente un objeto, porque tiene redefinido el operador []. Tiene una función miembro: length. Se puede utilizar este método para conocer la longitud de cualquier array.

int a[][] = new int[10][3];

a.length; /* 10 */

a[0].length; /* 3 */

Para crear un array en Java hay dos métodos básicos. Crear un array vacío:

int lista[] = new int[50];

o se puede crear ya el array con sus valores iniciales:

String nombres[] = {

"Juan","Pepe","Pedro","Maria"

};

Esto que es equivalente a:

String nombres[];

nombres = new String[4];

nombres[0] = new String( "Juan" );

nombres[1] = new String( "Pepe" );

nombres[2] = new String( "Pedro" );

nombres[3] = new String( "Maria" );

No se pueden crear arrays estáticos en tiempo de compilación:

int lista[50]; // generará un error en tiempo de compilación

Tampoco se puede rellenar un array sin declarar el tamaño con el operador new:

117

Page 118: Programacion en Java

int lista[];

for( int i=0; i < 9; i++ )

lista[i] = i;

Es decir, todos los arrays en Java son estáticos. Para convertir un array en el equivalente a un array dinámico en C/C++, se usa la clase vector, que permite operaciones de inserción, borrado, etc. en el array.

Operadores

Los operadores de Java son muy parecidos en estilo y funcionamiento a los de C. En la siguiente tabla aparecen los operadores que se utilizan en Java, por orden de precedencia:

. [] ()

++ --

! ~ instanceof

* / %

+ -

<< >> >>>

< > <= >= == !=

& ^ |

&& ||

? :

= op= (*= /= %= += -= etc.) ,

Los operadores numéricos se comportan como esperamos:

int + int = int

Los operadores relacionales devuelven un valor booleano.

Para las cadenas, se pueden utilizar los operadores relacionales para comparaciones además de + y += para la concatenación:

String nombre = "nombre" + "Apellido";

118

Page 119: Programacion en Java

El operador = siempre hace copias de objetos, marcando los antiguos para borrarlos, y ya se encargará el garbage collector de devolver al sistema la memoria ocupada por el objeto eliminado.

Separadores

Sólo hay un par de secuencias con otros caracteres que pueden aparecer en el código Java; son los separadores simples, que van a definir la forma y función del código. Los separadores admitidos en Java son:

() - paréntesis. Para contener listas de parámetros en la definición y llamada a métodos. También se utiliza para definir precedencia en expresiones, contener expresiones para control de flujo y rodear las conversiones de tipo.

{} - llaves. Para contener los valores de matrices inicializadas automáticamente. También se utiliza para definir un bloque de código, para clases, métodos y ámbitos locales.

[ ] - corchetes. Para declarar tipos matriz. También se utiliza cuando se referencian valores de matriz.

; - punto y coma. Separa sentencias.

, - coma. Separa identificadores consecutivos en una declaración de variables. También se utiliza para encadenar sentencias dentro de una sentencia for.

. - punto. Para separar nombres de paquete de subpaquetes y clases. También se utiliza para separar una variable o método de una variable de referencia.

3.2 Control de Flujo

Muchas de las sentencias de control del flujo del programa se han tomado del C:

Sentencias de Salto

if/else

if( Boolean ) {

sentencias;

}

else {

sentencias;

}

switch

switch( expr1 ) {

case expr2:

119

Page 120: Programacion en Java

sentencias;

break;

case expr3:

sentencias;

break;

default:

sentencias;

break;

}

Sentencias de Bucle

Bucles for

for( expr1 inicio; expr2 test; expr3 incremento ) {

sentencias;

}

El siguiente trocito de código Java que dibuja varias líneas en pantalla alternando sus colores entre rojo, azul y verde. Este fragmento sería parte de una función Java (método):

int contador;

for( contador=1; contador <= 12; contador++ ) {

switch( contador % 3 ) {

case 0:

setColor( Color.red );

break;

case 1:

setColor( Color.blue );

break;

case 2:

120

Page 121: Programacion en Java

setColor( Color.green );

break;

}

g.drawLine( 10,contador*10,80,contador*10 );

}

También se soporta el operador coma (,) en los bucles for

for( a=0,b=0; a < 7; a++,b+=2 )

Bucles while

while( Boolean ) {

sentencias;

}

Bucles do/while

do {

sentencias;

}while( Boolean );

Excepciones

try-catch-throw

try {

sentencias;

} catch( Exception ) {

sentencias;

}

Java implementa excepciones para facilitar la construcción de código robusto. Cuando ocurre un error en un programa, el código que encuentra el error lanza una excepción, que se puede capturar y recuperarse de ella. Java proporciona muchas excepciones predefinidas.

Control General del Flujo

break [etiqueta]

121

Page 122: Programacion en Java

continue [etiqueta]

return expr;

etiqueta: sentencia;

En caso de que nos encontremos con bucles anidados, se permite el uso de etiquetas para poder salirse de ellos, por ejemplo:

uno: for( )

{

dos: for( )

{

continue; // seguiría en el bucle interno

continue uno; // seguiría en el bucle principal

break uno; // se saldría del bucle principal

}

}

En el código de una función siempre hay que ser consecuentes con la declaración que se haya hecho de ella. Por ejemplo, si se declara una función para que devuelva un entero, es imprescindible que se coloque un return final para salir de esa función, independientemente de que haya otros en medio del código que también provoquen la salida de la función. En caso de no hacerlo se generará un Warning, y el código Java no se puede compilar con Warnings.

int func()

{

if( a == 0 )

return 1;

return 0; // es imprescindible porque se retorna un entero

}

3.3 Clases

Las clases son lo más simple de Java. Todo en Java forma parte de una clase, es una clase o describe como funciona una clase. El conocimiento de las clases es fundamental para poder entender los programas Java.

122

Page 123: Programacion en Java

Todas las acciones de los programas Java se colocan dentro del bloque de una clase o un objeto. Todos los métodos se definen dentro del bloque de la clase, Java no soporta funciones o variables globales. Esto puede despistar a los programadores de C++, que pueden definir métodos fuera del bloque de la clase, pero esta posibilidad es más un intento de no separarse mucho y ser compatible con C, que un buen diseño orientado a objetos. Así pues, el esqueleto de cualquier aplicación Java se basa en la definición de una clase.

Todos los datos básicos, como los enteros, se deben declarar en las clases antes de hacer uso de ellos. En C la unidad fundamental son los ficheros con código fuente, en Java son las clases. De hecho son pocas las sentencias que se pueden colocar fuera del bloque de una clase. La palabra clave import (equivalente al #include) puede colocarse al principio de un fichero, fuera del bloque de la clase. Sin embargo, el compilador reemplazará esa sentencia con el contenido del fichero que se indique, que consistirá, como es de suponer, en más clases.

Tipos de Clases

Hasta ahora sólo se ha utilizado la palabra clave public para calificar el nombre de las clases que hemos visto, pero hay tres modificadores más. Los tipos de clases que podemos definir son:

abstract

Una clase abstract tiene al menos un método abstracto. Una clase abstracta no se instancia, sino que se utiliza como clase base para la herencia.

final

Una clase final se declara como la clase que termina una cadena de herencia. No se puede heredar de una clase final. Por ejemplo, la clase Math es una clase final.

public

Las clases public son accesibles desde otras clases, bien sea directamente o por herencia. Son accesibles dentro del mismo paquete en el que se han declarado. Para acceder desde otros paquetes, primero tienen que ser importadas.

synchronizable

Este modificador especifica que todos los métodos definidos en la clase son sincronizados, es decir, que no se puede acceder al mismo tiempo a ellos desde distintos threads; el sistema se encarga de colocar los flags necesarios para evitarlo. Este mecanismo hace que desde threads diferentes se puedan modificar las mismas variables sin que haya problemas de que se sobreescriban.

3.4 Variables y Métodos de Instancia

Una clase en Java puede contener variables y métodos. Las variables pueden ser tipos primitivos como int, char, etc. Los métodos son funciones.

Por ejemplo, en el siguiente trozo de código podemos observarlo:

public MiClase {

int i;

123

Page 124: Programacion en Java

public MiClase() {

i = 10;

}

public void Suma_a_i( int j ) {

i = i + j;

}

}

La clase MiClase contiene una variable (i) y dos métodos, MiClase que es el constructor de la clase y Suma_a_i( int j ).

Ambito de una variable

Los bloques de sentencias compuestas en Java se delimitan con dos llaves. Las variables de Java sólo son válidas desde el punto donde están declaradas hasta el final de la sentencia compuesta que la engloba. Se pueden anidar estas sentencias compuestas, y cada una puede contener su propio conjunto de declaraciones de variables locales. Sin embargo, no se puede declarar una variable con el mismo nombre que una de ámbito exterior.

El siguiente ejemplo intenta declarar dos variables separadas con el mismo nombre. En C y C++ son distintas, porque están declaradas dentro de ámbitos diferentes. En Java, esto es ilegal.

Class Ambito {

int i = 1; // ámbito exterior

{ // crea un nuevo ámbito

int i = 2; // error de compilación

}

}

Métodos y Constructores

Los métodos son funciones que pueden ser llamadas dentro de la clase o por otras clases. El constructor es un tipo específico de método que siempre tiene el mismo nombre que la clase.

Cuando se declara una clase en Java, se pueden declarar uno o más constructores opcionales que realizan la inicialización cuando se instancia (se crea una ocurrencia) un objeto de dicha clase.

Utilizando el código de ejemplo anterior, cuando se crea una nueva instancia de MiClase, se crean (instancian) todos los métodos y variables, y se llama al constructor de la clase:

MiClase mc;

124

Page 125: Programacion en Java

mc = new MiClase();

La palabra clave new se usa para crear una instancia de la clase. Antes de ser instanciada con new no consume memoria, simplemente es una declaración de tipo. Después de ser instanciado un nuevo objeto mc, el valor de i en el objeto mc será igual a 10. Se puede referenciar la variable (de instancia) i con el nombre del objeto:

mc.i++; // incrementa la instancia de i de mc

Al tener mc todas las variables y métodos de MiClase, se puede usar la primera sintaxis para llamar al método Suma_a_i() utilizando el nuevo nombre de clase mc:

mc.Suma_a_i( 10 );

y ahora la variable mc.i vale 21.

Finalizadores

Java no utiliza destructores (al contrario que C++) ya que tiene una forma de recoger automáticamente todos los objetos que se salen del alcance. No obstante proporciona un método que, cuando se especifique en el código de la clase, el reciclador de memoria (garbage collector) llamará:

// Cierra el canal cuando este objeto es reciclado

protected void finalize() {

close();

}

3.5 Alcance de Objetos y Reciclado de Memoria

Los objetos tienen un tiempo de vida y consumen recursos durante el mismo. Cuando un objeto no se va a utilizar más, debería liberar el espacio que ocupaba en la memoria de forma que las aplicaciones no la agoten (especialmente las grandes).

En Java, la recolección y liberación de memoria es responsabilidad de un thread llamado automatic garbage collector (recolector automático de basura). Este thread monitoriza el alcance de los objetos y marca los objetos que se han salido de alcance. Veamos un ejemplo:

String s; // no se ha asignado todavia

s = new String( "abc" ); // memoria asignada

s = "def"; // se ha asignado nueva memoria

// (nuevo objeto)

Más adelante veremos en detalle la clase String, pero una breve descripción de lo que hace esto es; crear un objeto String y rellenarlo con los caracteres "abc" y crear otro (nuevo) String y colocarle los caracteres "def".

125

Page 126: Programacion en Java

En esencia se crean dos objetos:

Objeto String "abc"

Objeto String "def"

Al final de la tercera sentencia, el primer objeto creado de nombre s que contiene "abc" se ha salido de alcance. No hay forma de acceder a él. Ahora se tiene un nuevo objeto llamado s y contiene "def". Es marcado y eliminado en la siguiente iteración del thread reciclador de memoria.

3.6 Herencia

La Herencia es el mecanismo por el que se crean nuevos objetos definidos en términos de objetos ya existentes. Por ejemplo, si se tiene la clase Ave, se puede crear la subclase Pato, que es una especialización de Ave.

class Pato extends Ave {

int numero_de_patas;

}

La palabra clave extends se usa para generar una subclase (especialización) de un objeto. Una Pato es una subclase de Ave. Cualquier cosa que contenga la definición de Ave será copiada a la clase Pato, además, en Pato se pueden definir sus propios métodos y variables de instancia. Se dice que Pato deriva o hereda de Ave.

Además, se pueden sustituir los métodos proporcionados por la clase base. Utilizando nuestro anterior ejemplo de MiClase, aquí hay un ejemplo de una clase derivada sustituyendo a la función Suma_a_i():

import MiClase;

public class MiNuevaClase extends MiClase {

public void Suma_a_i( int j ) {

i = i + ( j/2 );

}

}

Ahora cuando se crea una instancia de MiNuevaClase, el valor de i también se inicializa a 10, pero la llamada al método Suma_a_i() produce un resultado diferente:

MiNuevaClase mnc;

mnc = new MiNuevaClase();

mnc.Suma_a_i( 10 );

126

Page 127: Programacion en Java

En Java no se puede hacer herencia múltiple. Por ejemplo, de la clase aparato con motor y de la clase animal no se puede derivar nada, sería como obtener el objeto toro mecánico a partir de una máquina motorizada (aparato con motor) y un toro (aminal). En realidad, lo que se pretende es copiar los métodos, es decir, pasar la funcionalidad del toro de verdad al toro mecánico, con lo cual no sería necesaria la herencia múltiple sino simplemente la compartición de funcionalidad que se encuentra implementada en Java a través de interfaces.

3.7 Control de Acceso

Cuando se crea una nueva clase en Java, se puede especificar el nivel de acceso que se quiere para las variables de instancia y los métodos definidos en la clase:

public

public void CualquieraPuedeAcceder(){}

Cualquier clase desde cualquier lugar puede acceder a las variables y métodos de instacia públicos.

protected

protected void SoloSubClases(){}

Sólo las subclases de la clase y nadie más puede acceder a las variables y métodos de instancia protegidos.

private

private String NumeroDelCarnetDeIdentidad;

Las variables y métodos de instancia privados sólo pueden ser accedidos desde dentro de la clase. No son accesibles desde las subclases.

friendly (sin declaración específica)

void MetodoDeMiPaquete(){}

Por defecto, si no se especifica el control de acceso, las variables y métodos de instancia se declaran friendly (amigas), lo que significa que son accesibles por todos los objetos dentro del mismo paquete, pero no por los externos al paquete. Es lo mismo que protected.

Los métodos protegidos (protected) pueden ser vistos por las clases derivadas, como en C++, y también, en Java, por los paquetes (packages). Todas las clases de un paquete pueden ver los métodos protegidos de ese paquete. Para evitarlo, se deben declarar como private protected, lo que hace que ya funcione como en C++ en donde sólo se puede acceder a las variables y métodos protegidos de las clases derivadas.

3.8 Variables y Métodos Estáticos

En un momento determinado se puede querer crear una clase en la que el valor de una variable de instancia sea el mismo (y de hecho sea la misma variable) para todos los objetos instanciados a

127

Page 128: Programacion en Java

partir de esa clase. Es decir, que exista una única copia de la variable de instancia. Se usará para ello la palabra clave static.

class Documento extends Pagina {

static int version = 10;

}

El valor de la variable version será el mismo para cualquier objeto instanciado de la clase Documento. Siempre que un objeto instanciado de Documento cambie la variable version, ésta cambiará para todos los objetos.

De la misma forma se puede declarar un método como estático, lo que evita que el método pueda acceder a las variables de instancia no estáticas:

class Documento extends Pagina {

static int version = 10;

int numero_de_capitulos;

static void annade_un_capitulo() {

numero_de_capitulos++; // esto no funciona

}

static void modifica_version( int i ) {

version++; // esto si funciona

}

}

La modificación de la variable numero_de_capitulos no funciona porque se está violando una de las reglas de acceso al intentar acceder desde un método estático a una variable no estática.

Todas las clases que se derivan, cuando se declaran estáticas, comparten la misma página de variables; es decir, todos los objetos que se generen comparten la misma zona de memoria. Las funciones estáticas se usan para acceder solamente a variables estáticas.

class UnaClase {

int var;

UnaClase()

{

var = 5;

128

Page 129: Programacion en Java

}

UnaFuncion()

{

var += 5;

}

}

En el código anterior, si se llama a la función UnaFuncion a través de un puntero a función, no se podría acceder a var, porque al utilizar un puntero a función no se pasa implícitamente el puntero al propio objeto (this). Sin embargo, sí se podría acceder a var si fuese estática, porque siempre estaría en la misma posición de memoria para todos los objetos que se creasen de UnaClase.

3.9 this Y super

Al acceder a variables de instancia de una clase, la palabra clave this hace referencia a los miembros de la propia clase. Volviendo al ejemplo de MiClase, se puede añadir otro constructor de la forma siguiente:

public class MiClase {

int i;

public MiClase() {

i = 10;

}

// Este constructor establece el valor de i

public MiClase( int valor ) {

this.i = valor; // i = valor

}

public void Suma_a_i( int j ) {

i = i + j;

}

}

Aquí this.i se refiere al entero i en la clase MiClase.

129

Page 130: Programacion en Java

Si se necesita llamar al método padre dentro de una clase que ha reemplazado ese método, se puede hacer referencia al método padre con la palabra clave super:

import MiClase;

public class MiNuevaClase extends MiClase {

public void Suma_a_i( int j ) {

i = i + ( j/2 );

super.Suma_a_i( j );

}

}

En el siguiente código, el constructor establecerá el valor de i a 10, después lo cambiará a 15 y finalmente el método Suma_a_i() de la clase padre (MiClase) lo dejará en 25:

MiNuevaClase mnc;

mnc = new MiNuevaClase();

mnc.Suma_a_i( 10 );

3.10 Clases Abstractas

Una de las características más útiles de cualquier lenguaje orientado a objetos es la posibilidad de declarar clases que definen como se utiliza solamente, sin tener que implementar métodos. Esto es muy útil cuando la implementación es específica para cada usuario, pero todos los usuarios tienen que utilizar los mismos métodos. Un ejemplo de clase abstracta en Java es la clase Graphics:

public abstract class Graphics {

public abstract void drawLine( int x1,int y1,int x2,

int y2 );

public abstract void drawOval( int x,int y,int width,

int height );

public abstract void drawArc( int x,int y,int width,

int height,int startAngle,int arcAngle );

..

}

130

Page 131: Programacion en Java

Los métodos se declaran en la clase Graphics, pero el código que ejecutará el método está en algún otro sitio:

public class MiClase extends Graphics {

public void drawLine( int x1,int y1,int x2,int y2 ) {

<código para pintar líneas -específico de

la arquitectura->

}

}

Cuando una clase contiene un método abstracto tiene que declararse abstracta. No obstante, no todos los métodos de una clase abstracta tienen que ser abstractos. Las clases abstractas no pueden tener métodos privados (no se podrían implementar) ni tampoco estáticos. Una clase abstracta tiene que derivarse obligatoriamente, no se puede hacer un new de una clase abstracta.

Una clase abstracta en Java es lo mismo que en C++ virtual func() = 0; lo que obliga a que al derivar de la clase haya que implementar forzosamente los métodos de esa clase abstracta.

3.11 Interfaces

Los métodos abstractos son útiles cuando se quiere que cada implementación de la clase parezca y funcione igual, pero necesita que se cree una nueva clase para utilizar los métodos abstractos.

Los interfaces proporcionan un mecanismo para abstraer los métodos a un nivel superior.

Un interface contiene una colección de métodos que se implementan en otro lugar. Los métodos de una clase son public, static y final.

La principal diferencia entre interface y abstract es que un interface proporciona un mecanismo de encapsulación de los protocolos de los métodos sin forzar al usuario a utilizar la herencia.

Por ejemplo:

public interface VideoClip {

// comienza la reproduccion del video

void play();

// reproduce el clip en un bucle

void bucle();

// detiene la reproduccion

void stop();

131

Page 132: Programacion en Java

}

Las clases que quieran utilizar el interface VideoClip utilizarán la palabra implements y proporcionarán el código necesario para implementar los métodos que se han definido para el interface:

class MiClase implements VideoClip {

void play() {

<código>

}

void bucle() {

<código>

}

void stop() {

<código>

}

Al utilizar implements para el interface es como si se hiciese una acción de copiar-y-pegar del código del interface, con lo cual no se hereda nada, solamente se pueden usar los métodos.

La ventaja principal del uso de interfaces es que una clase interface puede ser implementada por cualquier número de clases, permitiendo a cada clase compartir el interfaz de programación sin tener que ser consciente de la implementación que hagan las otras clases que implementen el interface.

class MiOtraClase implements VideoClip {

void play() {

<código nuevo>

}

void bucle() {

<código nuevo>

}

void stop() {

<código nuevo>

132

Page 133: Programacion en Java

}

3.12 Métodos Nativos

Java proporciona un mecanismo para la llamada a funciones C y C++ desde nuestro código fuente Java. Para definir métodos como funciones C o C++ se utiliza la palabra clave native.

public class Fecha {

int ahora;

public Fecha() {

ahora = time();

}

private native int time();

static {

System.loadLibrary( "time" );

}

}

Una vez escrito el código Java, se necesitan ejecutar los pasos siguientes para poder integrar el código C o C++:

• Utilizar javah para crear un fichero de cabecera (.h) • Utilizar javah para crear un fichero de stubs, es decir, que contiene la declaración de las

funciones • Escribir el código del método nativo en C o C++, es decir, rellenar el código de la función,

completando el trabajo de javah al crear el fichero de stubs • Compilar el fichero de stubs y el fichero .c en una librería de carga dinámica (DLL en

Windows '95 o libXX.so en Unix) • Ejecutar la aplicación con el appletviewer

Más adelante trataremos en profundidad los métodos nativos, porque añaden una gran potencia a Java, al permitirle integrar a través de librería dinámica cualquier algoritmo desarrollado en C o C++, lo cual, entre otras cosas, se utiliza como método de protección contra la descompilación completa del código Java.

3.13 Paquetes

La palabra clave package permite agrupar clases e interfaces. Los nombres de los paquetes son palabras separadas por puntos y se almacenan en directorios que coinciden con esos nombres. Por ejemplo, los ficheros siguientes, que contienen código fuente Java:

Applet.java, AppletContext.java, AppletStub.java, AudioClip.java

133

Page 134: Programacion en Java

contienen en su código la línea:

package java.applet;

Y las clases que se obtienen de la compilación de los ficheros anteriores, se encuentran con el nombre nombre_de_clase.class, en el directorio:

java/applet

Import

Los paquetes de clases se cargan con la palabra clave import, especificando el nombre del paquete como ruta y nombre de clase (es lo mismo que #include de C/C++). Se pueden cargar varias clases utilizando un asterisco.

import java.Date;

import java.awt.*;

Si un fichero fuente Java no contiene ningún package, se coloca en el paquete por defecto sin nombre. Es decir, en el mismo directorio que el fichero fuente, y la clase puede ser cargada con la sentencia import:

import MiClase;

Paquetes de Java

El lenguaje Java proporciona una serie de paquetes que incluyen ventanas, utilidades, un sistema de entrada/salida general, herramientas y comunicaciones. En la versión actual del JDK, los paquetes Java que se incluyen son:

java.applet

Este paquete contiene clases diseñadas para usar con applets. Hay una clase Applet y tres interfaces: AppletContext, AppletStub y AudioClip.

java.awt

El paquete Abstract Windowing Toolkit (awt) contiene clases para generar widgets y componentes GUI (Interfaz Gráfico de Usuario). Incluye las clases Button, Checkbox, Choice, Component, Graphics, Menu, Panel, TextArea y TextField.

java.io

El paquete de entrada/salida contiene las clases de acceso a ficheros: FileInputStream y FileOutputStream.

java.lang

Este paquete incluye las clases del lenguaje Java propiamente dicho: Object, Thread, Exception, System, Integer, Float, Math, String, etc.

134

Page 135: Programacion en Java

java.net

Este paquete da soporte a las conexiones del protocolo TCP/IP y, además, incluye las clases Socket, URL y URLConnection.

java.util

Este paquete es una miscelánea de clases útiles para muchas cosas en programación. Se incluyen, entre otras, Date (fecha), Dictionary (diccionario), Random (números aleatorios) y Stack (pila FIFO).

3.14 Referencias

Java se asemeja mucho a C y C++. Esta similitud, evidentemente intencionada, es la mejor herramienta para los programadores, ya que facilita en gran manera su transición a Java. Desafortunadamente, tantas similitudes hacen que no nos paremos en algunas diferencias que son vitales. La terminología utilizada en estos lenguajes, a veces es la misma, pero hay grandes diferencias subyacentes en su significado.

C tiene tipos de datos básicos y punteros. C++ modifica un poco este panorama y le añade los tipos referencia. Java también especifica sus tipos primitivos, elimina cualquier tipo de punteros y tiene tipos referencia mucho más claros.

Todo este maremágnum de terminología provoca cierta consternación, así que vamos a intentar aclarar lo que realmente significa.

Conocemos ya ampliamente todos los tipos básicos de datos: datos base, integrados, primitivos e internos; que son muy semejantes en C, C++ y Java; aunque Java simplifica un poco su uso a los desarrolladores haciendo que el chequeo de tipos sea bastante más rígido. Además, Java añade los tipos boolean y hace imprescindible el uso de este tipo booleano en sentencias condicionales.

4. PROGRAMAS BÁSICOS EN JAVA

4.1 Una mínima aplicación en java

La aplicación más pequeña posible es la que simplemente imprimir un mensaje en la pantalla. Tradicionalmente, el mensaje suele ser "Hola Mundo!". Esto es justamente lo que hace el siguiente fragmento de código:

//

// Aplicación HolaMundo de ejemplo

//

class HolaMundoApp {

public static void main( String args[] ) {

System.out.println( "Hola Mundo!" ) ;

}

135

Page 136: Programacion en Java

}

HolaMundo

Vamos ver en detalle la aplicación anterior, línea a línea. Esas líneas de código contienen los componenetes mínimos para imprimir Hola Mundo! en la pantalla.

//

// Aplicación HolaMundo de ejemplo

//

Estas tres primera líneas son comentarios. Hay tres tipos de comentarios en Java, // es un comentario orientado a línea.

class HolaMundoApp {

Esta línea declara la clase HolaMundoApp. El nombre de la clase especificado en el fichero fuente se utiliza para crear un fichero nombredeclase.class en el directorio en el que se compila la aplicación. En nuestro caso, el compilador creará un fichero llamado HolaMundoApp.class.

public static void main( String args[] ) {

Esta línea especifica un método que el intérprete Java busca para ejecutar en primer lugar. Igual que en otros lenguajes, Java utiliza una palabra clave main para especificar la primera función a ejecutar. En este ejemplo tan simple no se pasan argumentos.

public significa que el método main puede ser llamado por cualquiera, incluyendo el intérprete Java.

static es una palabra clave que le dice al compilador que main se refiere a la propia clase HolaMundoApp y no a ninguna instancia de la clase. De esta forma, si alguien intenta hacer otra instancia de la clase, el método main no se instanciaría.

void indica que main no devuelve nada. Esto es importante ya que Java realiza una estricta comprobación de tipos, incluyendo los tipos que se ha declarado que devuelven los métodos.

args[] es la declaración de un array de Strings. Estos son los argumentos escritos tras el nombre de la clase en la línea de comandos:

%java HolaMundoApp arg1 arg2 ...

System.out.println( "Hola Mundo!" );

Esta es la funcionalidad de la aplicación. Esta línea muestra el uso de un nombre de clase y método. Se usa el método println() de la clase out que está en el paquete System.

El método println() toma una cadena como argumento y la escribe en el stream de salida estándar; en este caso, la ventana donde se lanza la aplicación.

}

136

Page 137: Programacion en Java

}

Finalmente, se cierran las llaves que limitan el método main() y la clase HolaMundoApp.

Compilacion y Ejecucion de HolaMundo

Vamos a ver a continuación como podemos ver el resultado de nuestra primera aplicación Java en pantalla. Generaremos un fichero con el código fuente de la aplicación, lo compilaremos y utilizaremos el intérprete java para ejecutarlo.

Ficheros Fuente Java

Los ficheros fuente en Java terminan con la extensión ".java". Crear un fichero utilizando cualquier editor de texto ascii que tenga como contenido el código de las ocho líneas de nuestra mínima aplicación, y salvarlo en un fichero con el nombre de HolaMundoApp.java. Para crear los ficheros con código fuente Java no es necesario un procesador de textos, aunque puede utilizarse siempre que tenga salida a fichero de texto plano o ascii, sino que es suficiente con cualquier otro editor.

Compilación

El compilador javac se encuentra en el directorio bin por debajo del directorio java, donde se haya instalado el JDK. Este directorio bin, si se han seguido las instrucciones de instalación, debería formar parte de la variable de entorno PATH del sistema. Si no es así, tendría que revisar la Instalación del JDK. El compilador de Java traslada el código fuente Java a byte-codes, que son los componentes que entiende la Máquina Virtual Java que está incluida en los navegadores con soporte Java y en appletviewer.

Una vez creado el fichero fuente HolaMundoApp.java, se puede compilar con la línea siguiente:

%javac HolaMundoApp.java

Si no se han cometido errores al teclear ni se han tenido problemas con el path al fichero fuente ni al compilador, no debería aparecer mensaje alguno en la pantalla, y cuando vuelva a aparecer el prompt del sistema, se debería ver un fichero HolaMundoApp.class nuevo en el directorio donde se encuentra el fichero fuente.

Si ha habido algún problema, en Problemas de compilación al final de esta sección, hemos intentado reproducir los que más frecuentemente se suelen dar, se pueden consultar por si pueden aportar un poco de luz al error que haya aparecido.

Ejecución

Para ejecutar la aplicación HolaMundoApp, hemos de recurrir al intérprete java, que también se encuentra en el directorio bin, bajo el directorio java. Se ejecutará la aplicación con la línea:

%java HolaMundoApp

y debería aparecer en pantalla la respuesta de Java:

%Hola Mundo!

137

Page 138: Programacion en Java

El símbolo % representa al prompt del sistema, y lo utilizaremos para presentar las respuestas que nos ofrezca el sistema como resultado de la ejecución de los comandos que se indiquen en pantalla o para indicar las líneas de comandos a introducir.

Problemas de compilación

A continuación presentamos una lista de los errores más frecuentes que se presentan a la hora de compilar un fichero con código fuente Java, nos basaremos en errores provocados sobre nuestra mínima aplicación Java de la sección anterior, pero podría generalizarse sin demasiados problemas.

%javac: Command not found

No se ha establecido correctamente la variable PATH del sistema para el compilador javac. El compilador javac se encuentra en el directorio bin, que cuelga del directorio java, que cuelga del directorio donde se haya instalado el JDK (Java Development Kit).

%HolaMundoApp.java:3: Method printl(java.lang.String) not found in class java.io.PrintStream.

System.out.printl( "HolaMundo!);

^

• Error tipográfico, el método es println no printl.

%In class HolaMundoApp: main must be public and static

• Error de ejecución, se olvidó colocar la palabra static en la declaración del método main de la aplicación.

%Can´t find class HolaMundoApp

Este es un error muy sutil. Generalmente significa que el nombre de la clase es distinto al del fichero que contiene el código fuente, con lo cual el fichero nombre_fichero.class que se genera es diferente del que cabría esperar. Por ejemplo, si en nuestro fichero de código fuente de nuestra aplicación HolaMundoApp.java colocamos en vez de la declaración actual de la clase HolaMundoApp, la línea:

class HolaMundoapp {

se creará un fichero HolaMundoapp.class, que es diferente del HolaMundoApp.class, que es el nombre esperado de la clase; la diferencia se encuentra en la a minúscula y mayúscula.

4.2 El Visor de applets de Sun (appletviewer)

El visualizador de applets (appletviewer) es una aplicación que permite ver en funcionamiento applets, sin necesidad de la utilización de un navegador World-Wide-Web como HotJava, Microsoft Explorer o Nescape. En adelante, recurriremos muchas veces a él, ya que el objetivo del tutorial es el lenguaje Java.

Applet

138

Page 139: Programacion en Java

La definición más extendida de applet, muy bien resumida por Patrick Naughton, indica que un applet es "una pequeña aplicación accesible en un servidor Internet, que se transporta por la red, se instala automáticamente y se ejecuta in situ como parte de un documento web". Claro que así la definición establece el entorno (Internet, Web, etc.). En realidad, un applet es una aplicación pretendidamente corta (nada impide que ocupe más de un gigabyte, a no ser el pensamiento de que se va a transportar por la red y una mente sensata) basada en un formato gráfico sin representación independiente: es decir, se trata de un elemento a embeber en otras aplicaciones; es un componente en su sentido estricto.

Un ejemplo en otro ámbito de cosas podría ser el siguiente: Imaginemos una empresa, que cansada de empezar siempre a codificar desde cero, diseña un formulario con los datos básicos de una persona (nombre, dirección, etc.). Tal formulario no es un diálogo por sí mismo, pero se podría integrar en diálogos de clientes, proveedores, empleados, etc.

El hecho de que se integre estática (embebido en un ejecutable) o dinámicamente (intérpretes, DLLs, etc.) no afecta en absoluto a la esencia de su comportamiento como componente con que construir diálogos con sentido autónomo.

Pues bien, así es un applet. Lo que ocurre es que, dado que no existe una base adecuada para soportar aplicaciones industriales Java en las que insertar nuestras miniaplicaciones (aunque todo se andará), los applets se han construido mayoritariamente, y con gran acierto comercial (parece), como pequeñas aplicaciones interactivas, con movimiento, luces y sonido... en Internet.

Llamadas a Applets con appletviewer

Un applet es una mínima aplicación Java diseñada para ejecutarse en un navegador Web. Por tanto, no necesita preocuparse por un método main() ni en dónde se realizan las llamadas. El applet asume que el código se está ejecutando desde dentro de un navegador.

El appletviewer se asemeja al mínimo navegador. Espera como argumento el nombre del fichero html que debe cargar, no se le puede pasar directamente un programa Java. Este fichero html debe contener una marca que especifica el código que cargará el appletviewer:

<HTML>

<APPLET CODE=HolaMundo.class WIDTH=300 HEIGHT=100>

</APPLET>

</HTML>

El appletviewer crear un espacio de navegación, incluyendo un área gráfica, donde se ejecutará el applet, entonces llamará a la clase applet apropiada. En el ejemplo anterior, el appletviewer cargará una clase de nombre HolaMundo y le permitirá trabajar en su espacio gráfico.

Arquitectura de appletviewer

El appletviewer representa el mínimo interfaz de navegación. En la figura se muestran los pasos que seguiría appletviewer para presentarnos el resultado de la ejecución del código de nuestra clase.

139

Page 140: Programacion en Java

Esta es una visión simplificada del appletviewer. La función principal de esta aplicación es proporcionar al usuario un objeto de tipo Graphics sobre el que dibujar, y varias funciones para facilitar el uso del objeto Graphics.

Ciclo de vida de un Applet

Cuando un applet se carga en el appletviewer, comienza su ciclo de vida, que pasaría por las siguientes fases:

Se crea una instancia de la clase que controla el applet. En el ejemplo de la figura anterior, sería la clase HolaMundo.

El applet se incializa.

El applet comienza a ejecutarse.

El applet empieza a recibir llamadas. Primero recibe una llamada init (inicializar), seguida de un mensaje start (empezar) y paint (pintar). Estas llamadas pueden ser recibidas asíncronamente.

4.3 Escribir Applets Java

Para escribir applets Java, hay que utilizar una serie de métodos, algunos de los cuales ya se hay sumariado al hablar de los métodos del appletviewer, que es el visualizador de applets de Sun. Incluso para el applet más sencillo necesitaremos varios métodos. Son los que se usan para arrancar (start) y detener (stop) la ejecución del applet, para pintar (paint) y actualizar (update) la pantalla y para capturar la información que se pasa al applet desde el fichero HTML a través de la marca APPLET.

init ( )

Esta función miembro es llamada al crearse el applet. Es llamada sólo una vez. La clase Applet no hace nada en init(). Las clases derivadas deben sobrecargar este método para cambiar el tamaño

140

Page 141: Programacion en Java

durante su inicialización, y cualquier otra inicialización de los datos que solamente deba realizarse una vez. Deberían realizarse al menos las siguientes acciones:

Carga de imágenes y sonido

El resize del applet para que tenga su tamaño correcto

Asignación de valores a las variables globales

Por ejemplo:

public void init() {

if( width < 200 || height < 200 )

resize( 200,200 );

valor_global1 = 0;

valor_global2 = 100;

// cargaremos imágenes en memoria sin mostrarlas

// cargaremos música de fondo en memoria sin reproducirla

}

destroy ( )

Esta función miembro es llamada cuando el applet no se va a usar más. La clase Applet no hace nada en este método. Las clases derivadas deberían sobrecargarlo para hacer una limpieza final. Los applet multithread deberán usar destroy() para "matar" cuanquier thread del applet que quedase activo.

start ( )

Llamada para activar el applet. Esta función miembro es llamada cuando se visita el applet. La clase Applet no hace nada en este método. Las clases derivadas deberían sobrecargarlo para comenzar una animación, sonido, etc.

public void start() {

estaDetenido = false;

// comenzar la reproducción de la música

musicClip.play();

}

141

Page 142: Programacion en Java

También se puede utilizar start() para eliminar cualquier thread que se necesite.

stop ( )

Llamada para detener el applet. Se llama cuando el applet desaparece de la pantalla. La clase Applet no hace nada en este método. Las clases derivadas deberían sobrecargarlo para detener la animación, el sonido, etc.

public void stop() {

estaDetenido = true;

if( /* ¿se está reproduciendo música? */ )

musicClip.stop();

}

resize ( int width,int height )

El método init() debería llamar a esta función miembro para establecer el tamaño del applet. Puede utilizar las variables ancho y alto, pero no es necesario. Cambiar el tamaño en otro sitio que no sea init() produce un reformateo de todo el documento y no se recomienda.

En el navegador Netscape, el tamaño del applet es el que se indica en la marca APPLET del HTML, no hace caso a lo que se indique desde el código Java del applet.

width

Variable entera, su valor es el ancho definido en el parámetro WIDTH de la marca HTML del APPLET. Por defecto es el ancho del icono.

height

Variable entera, su valor es la altura definida en el parámetro HEIGHT de la marca HTML del APPLET. Por defecto es la altura del icono. Tanto width como height están siempre disponibles para que se puede chequear el tamaño del applet.

Podemos retomar el ejemplo de init():

public void init() {

if( width < 200 || height < 200 )

resize( 200,200 );

...

paint( Graphics g )

142

Page 143: Programacion en Java

Se llama cada vez que se necesita refrescar el área de dibujo del applet. La clase Applet simplemente dibuja una caja con sombreado de tres dimensiones en el área. Obviamente, la clase derivada debería sobrecargar este método para representar algo inteligente en la pantalla.

Para repintar toda la pantalla cuando llega un evento Paint, se pide el rectángulo sobre el que se va a aplicar paint() y si es más pequeño que el tamaño real del applet se invoca a repaint(), que como va a hacer un update(), se actualizará toda la pantalla.

Podemos utilizar paint() para imprimir nuestro mensaje de bienvenida:

void public paint( Graphics g ) {

g.drawString( "Hola Java!",25,25 );

// Dibujaremos la imágenes que necesitemos

}

update( Graphics g )

Esta es la función que se llama realmente cuando se necesita actualizar la pantalla. La clase Applet simplemente limpia el área y llama al método paint(). Esta funcionalidad es suficiente en la mayoría de los casos. De cualquier forma, las clases derivadas pueden sustituir esta funcionalidad para sus propósitos.

Podemos, por ejemplo, utilizar update() para modificar selectivamente partes del área gráfica sin tener que pintar el área completa:

public void update( Graphics g ) {

if( estaActualizado )

{

g.clear(); // garantiza la pantalla limpia

repaint(); // podemos usar el método padre: super.update()

}

else

// Información adicional

g.drawString( "Otra información",25,50 );

}

repaint()

A esta función se la debería llamar cuando el applet necesite ser repintado. No debería sobrecargarse, sino dejar que Java repinte completamente el contenido del applet.

143

Page 144: Programacion en Java

Al llamar a repaint(), sin parámetros, internamente se llama a update() que borrará el rectángulo sobre el que se redibujará y luego se llama a paint(). Como a repaint() se le pueden pasar parámetros, se puede modificar el rectángulo a repintar.

getParameter ( String attr )

Este método carga los valores parados al applet vía la marca APPLET de HTML. El argumento String es el nombre del parámetro que se quiere obtener. Devuelve el valor que se le haya asignado al parámetro; en caso de que no se le haya asignado ninguno, devolverá null.

Para usar getParameter(), se define una cadena genérica. Una vez que se ha capturado el parámetro, se utilizan métodos de cadena o de números para convertir el valor obtenido al tipo adecuado.

public void init() {

String pv;

pv = getParameter( "velocidad" );

if( pv == null )

velocidad = 10;

else

velocidad = Integer.parseInt( pv );

}

getDocumentBase ( )

Indica la ruta http, o el directorio del disco, de donde se ha recogido la página HTML que contiene el applet, es decir, el lugar donde está la hoja en todo Internet o en el disco.

getCodeBase ( )

Indica la ruta http, o el directorio del disco, de donde se ha cargado el código bytecode que forma el applet, es decir, el lugar donde está el fichero .class en todo Internet o en el disco.

print ( Graphics g )

Para imprimir en impresora, al igual que paint() se puede utilizar print(), que pintará en la impresora el mapa de bits del dibujo.

5. EL DEPURADOR DE JAVA - jdb

El depurador de Java, jdb es un depurador de línea de comandos, similar al que Sun proporciona en sus Sistemas, dbx. Es complicado de utilizar y un tanto críptico, por lo que, en principio, tiene escasa practicidad y es necesaria una verdadera emergencia para tener que recurrir a él.

144

Page 145: Programacion en Java

Trataremos por encima los comandos que proporciona el jdb, pero sin entrar en detalles de su funcionamiento, porque no merece la pena. Casi es mejor esperar a disponer de herramientas visuales para poder depurar con cierta comodidad nuestro código Java.

Para poder utilizar el depurador, las aplicaciones Java deben estar compiladas con la opción de depuración activada, -g. Posteriormente se puede lanzar appletviewer con la opción de depuración, debug, y habremos puesto en marcha jdb.

5.1 Depurar HolaMundo

Hemos modificado nuestro applet de ejemplo para utilizarlo en nuestra sesión de ejemplo con el depurador. Se compilaría con el comando:

%javac -g hm.java

y el contenido de nuestro applet HolaMundo modificado y guardado en el fichero hm.java sería el siguiente:

//

// Applet HolaMundo de ejemplo, para depurar

//

import java.awt.Graphics;

import java.applet.Applet;

public class hm extends Applet {

int i;

public void paint( Graphics g ) {

i = 10;

g.drawString( "Hola Mundo!",25,25 );

}

}

Una vez compilado, iniciamos la sesión lanzando el visor de applets de Sun con la opción de depuración, utilizando el comando:

%appletviewer -debug hm.html

El fichero hm.html contiene las líneas mínimas para poder activar el applet, estas líneas son las que reproducimos:

<html>

145

Page 146: Programacion en Java

<applet code=hm.class width=100 height=100>

</applet>

</html>

Se inicia pues la sesión con el depurador y vamos a ir reproduciendo lo que aparece en la pantalla a medida que vamos introduciendo comandos:

%appletviewer -debug hm.html

Loading jdb...

0xee301bf0:class(sun.applet.AppletViewer)

>

5.2 Comando help

El comando help proporciona una lista de los comandos que están disponibles en la sesión de jdb. Esta lista es la que sigue, en donde hemos aprovechado la presencia de todos los comandos para comentar la acción que cada uno de ellos lleva a cabo.

>help

** command list **

threads [threadgroup] -- lista threads

thread <thread id> -- establece el thread por defecto

suspend [thread id(s)] -- suspende threads (por defecto, todos)

resume [thread id(s)] -- continúa threads (por defecto, todos)

where [thread id]|all -- muestra la pila de un thread

threadgroups -- lista los grupos de threads

threadgroup <name> -- establece el grupo de thread actual

print <id> [id(s)] -- imprime un objeto o campo

dump <id> [id(s)] -- imprime toda la información del objeto

locals -- imprime las variables locales de la pila actual

classes -- lista las clases conocidas

methods <class id> -- lista los métodos de una clase

146

Page 147: Programacion en Java

stop in <class id>.<method> -- fija un punto de ruptura en un método

stop at <class id>:<line> -- establece un punto de ruptura en una línea

up [n frames] -- ascender en la pila de threads

down [n frames] -- descender en la pila de threads

clear <class id>:<line> -- eliminar un punto de ruptura

step -- ejecutar la línea actual

cont -- continuar la ejecución desde el punto de ruptura

catch <class id> -- parar por la excepción especificada

ignore <class id> -- ignorar la excepción especificada

list [line number] -- imprimir código fuente

use [source file path] -- ver o cambiar la ruta del fichero fuente

memory -- informe del uso de la memoria

load <classname> - carga la clase Java a ser depurada

run <args> - comienza la ejecución de la clase cargada

!! - repite el último comando

help (or ?) - lista los comandos

exit (or quit) - salir del depurador

>

5.3 Comando threadgroups

El comando threadgroups permite ver la lista de threads que se están ejecutando. Los grupos system y main deberían estar siempre corriendo.

>threadgroups

1.(java.lang.ThreadGroup)0xee300068 system

2.(java.lang.ThreadGroup)0xee300a98 main

>

5.4 Comando threads

147

Page 148: Programacion en Java

El comando threads se utiliza para ver la lista completa de los threads que se están ejecutando actualmente.

>threads

Group system:

1.(java.lang.Thread)0xee300098 clock handler cond

2.(java.lang.Thread)0xee300558 Idle thread run

3.(java.lang.Thread)0xee3005d0 sync Garbage Collector cond

4.(java.lang.Thread)0xee300620 Finalizer thread cond

5.(java.lang.Thread)0xee300a20 Debugger agent run

6.(java.tools.debug.BreakpointHandler)0xee300b58) Breakpoint handler cond

Group main:

7.(java.lang.Thread)0xee300048 main suspended

>

5.5 Comando run

El comando run es el que se utiliza para arrancar el appletviewer en la sesión de depuración. Lo teclearemos y luego volveremos a listar los threads que hay en ejecución.

>run

run sun.applet.AppletViewer hm.html

running...

main[1]threads

threads

Group sun.applet.AppletViewer.main:

1.(java.lang.Thread)0xee3000c0 AWT-Motif running

2.(sun.awt.ScreenUpdater)0xee302ed0 ScreenUpdater cond. Waiting

Group applet-hm.class:

3.(java.lang.Thread)0xee302f38 Thread-6 cond. Waiting

main[1]

148

Page 149: Programacion en Java

El visor de applets de Sun aparecerá en la pantalla y mostrará el conocido mensaje de saludo al Mundo. Ahora vamos a rearrancar el appletviewer con un punto de ruptura, para detener la ejecución del applet, y podamos seguir mostrando los comandos disponibles en el jdb.

main[1]exit

%appletviewer -debug hm.html

Loading jdb...

0xee3009c8:class(sun.applet.AppletViewer)

>stop in hm.paint

Breakpoint set in hm.paint

>run

run sun.applet.AppletViewer hm.html

running...

Breakpoint hit: hm.paint(hm.java:9)

AWT-Motif[1]

5.6 Comando where

El comando where mostrará la pila de ejecución del applet.

AWT-Motif[1]where

[1]hm.paint(hm.java:9)

[2]sun.awt.motif.MComponentPeer.paint(MComponenetPeer.java:109)

[3]sun.awt.motif.MComponentPeer.handleExpose(MComponenetPeer.java:170)

AWT-Motif[1]

5.7 Comando use

El comando use nos informa del camino donde jdb va a buscar los ficheros fuentes que contienen el código Java de las clases que se están depurando. Por defecto, utilizará el camino que se especifique en la variable de entorno CLASSPATH.

AWT-Motif[1]use

/usr/local/java/classes:

AWT-Motif[1]

149

Page 150: Programacion en Java

5.8 Comando list

El comando list mostrará el código fuente actual al comienzo del punto de ruptura que hayamos fijado.

AWT-Motif[1]list

9 public void paint( Graphics g ) {

10 => i = 10;

11 g.drawString( "Hola Mundo!",25,25 ) ;

12 }

13 }

AWT-Motif[1]

5.9 Comando dump

El comando dump nos permitirá ahora ver el valor del objeto g pasado desde el appletviewer.

AWT-Motif[1]dump g

g = (sun.awt.motif.X11Graphics)0xee303df8 {

int pData = 1342480

Color foreground = (java.awt.Color)0xee302378

Font font = (java.awt.Font)0xee302138

int originX = 0

int originY = 0

float scaleX = 1

float scaleY = 1

Image image = null

}

AWT-Motif[1]

5.10 Comando step

El comando step nos porporciona el método para ejecutar la línea actual, que estará siendo apuntada por el indicador si hemos utilizado el comando list.

150

Page 151: Programacion en Java

AWT-Motif[1]step

Breakpoint hit: hm.paint(hm.java:11)

AWT-Motif[1]list

9 public void paint( Graphics g ) {

10 i = 10;

11 => g.drawString( "Hola Mundo!",25,25 );

12 }

13 }

AWT-Motif[1]

6. AWT

6.1 Introducción al AWT

AWT es el acrónimo del X Window Toolkit para Java, donde X puede ser cualquier cosa: Abstract, Alternative, Awkward, Another o Asqueroso; aunque parece que Sun se decanta por Abstracto, seriedad por encima de todo. Se trata de una biblioteca de clases Java para el desarrollo de Interfaces de Usuario Gráficas. La versión del AWT que Sun proporciona con el JDK se desarrolló en sólo dos meses y es la parte más débil de todo lo que representa Java como lenguaje. El entorno que ofrece es demasiado simple, no se han tenido en cuenta las ideas de entornos gráficos novedosos, sino que se ha ahondado en estructuras orientadas a eventos, llenas de callbacks y sin soporte alguno del entorno para la construcción gráfica; veremos que la simple acción de colocar un dibujo sobre un botón se vuelve una tarea harto complicada. Quizá la presión de tener que lanzar algo al mercado haya tenido mucho que ver en la pobreza de AWT.

JavaSoft, asegura que esto sólo era el principio y que AWT será multi-idioma, tendrá herramientas visuales, etc. En fin, al igual que dicen los astrólogos, el futuro nos deparará muchas sorpresas.

La estructura básica del AWT se basa en Componentes y Contenedores. Estos últimos contienen Componentes posicionados a su respecto y son Componentes a su vez, de forma que los eventos pueden tratarse tanto en Contenedores como en Componentes, corriendo por cuenta del programador (todavía no hay herramientas de composición visual) el encaje de todas las piezas, así como la seguridad de tratamiento de los eventos adecuados. Nada trivial.

No obstante y pese a ello, vamos a abordar en este momento la programación con el AWT para tener la base suficiente y poder seguir profundizando en las demás características del lenguaje Java, porque como vamos a ir presentando ejemplos gráficos es imprescindible el conocimiento del AWT. Mientras tanto, esperemos que JavaSoft sea fiel a sus predicciones y lo que ahora veamos nos sirva de base para migrar a un nuevo y maravilloso AWT.

6.2 Interface de Usuario

La interface de usuario es la parte del programa que permite a éste interactuar con el usuario. Las interfaces de usuario pueden adoptar muchas formas, que van desde la simple línea de comandos hasta las interfaces gráficas que proporcionan las aplicaciones más modernas.

151

Page 152: Programacion en Java

La interface de usuario es el aspecto más importante de cualquier aplicación. Una aplicación sin un interfaz fácil, impide que los usuarios saquen el máximo rendimiento del programa. Java proporciona los elementos básicos para construir decentes interfaces de usuario a través del AWT.

Al nivel más bajo, el sistema operativo transmite información desde el ratón y el teclado como dispositivos de entrada al programa. El AWT fue diseñado pensando en que el programador no tuviese que preocuparse de detalles como controlar el movimiento del ratón o leer el teclado, ni tampoco atender a detalles como la escritura en pantalla. El AWT constituye una librería de clases orientada a objeto para cubrir estos recursos y servicios de bajo nivel.

Debido a que el lenguaje de programación Java es independiente de la plataforma en que se ejecuten sus aplicaciones, el AWT también es independiente de la plataforma en que se ejecute. El AWT proporciona un conjunto de herramientas para la construcción de interfaces gráficas que tienen una apariencia y se comportan de forma semejante en todas las plataformas en que se ejecute. Los elementos de interface proporcionados por el AWT están implementados utilizando toolkits nativos de las plataformas, preservando una apariencia semejante a todas las aplicaciones que se creen para esa plataforma. Este es un punto fuerte del AWT, pero también tiene la desventaja de que una interface gráfica diseñada para una plataforma, puede no visualizarse correctamente en otra diferente.

6.3 Estructura del AWT

La estructura de la versión actual del AWT podemos resumirla en los puntos que exponemos a continuación:

• Los Contenedores contienen Componentes, que son los controles básicos • No se usan posiciones fijas de los Componentes, sino que están situados a través de una

disposición controlada (layouts) • El común denominador de más bajo nivel se acerca al teclado, ratón y manejo de eventos • Alto nivel de abstracción respecto al entorno de ventanas en que se ejecute la aplicación

(no hay áreas cliente, ni llamadas a X, ni hWnds, etc.) • La arquitectura de la aplicación es dependiente del entorno de ventanas, en vez de tener

un tamaño fijo • Es bastante dependiente de la máquina en que se ejecuta la aplicación (no puede asumir

que un diálogo tendrá el mismo tamaño en cada máquina) • Carece de un formato de recursos. No se puede separar el código de lo que es

propiamente interface. No hay ningún diseñador de interfaces (todavía)

6.4 Componentes y Contenedores

Una interface gráfica está construida en base a elementos gráficos básicos, los Componentes. Típicos ejemplos de estos Componentes son los botones, barras de desplazamiento, etiquetas, listas, cajas de selección o campos de texto. Los Componentes permiten al usuario interactuar con la aplicación y proporcionar información desde el programa al usuario sobre el estado del programa. En el AWT, todos los Componentes de la interface de usuario son instancias de la clase Component o uno de sus subtipos.

Los Componentes no se encuentran aislados, sino agrupados dentro de Contenedores. Los Contenedores contienen y organizan la situación de los Componentes; además, los Contenedores son en sí mismos Componentes y como tales pueden ser situados dentro de otros Contenedores. También contienen el código necesario para el control de eventos, cambiar la forma del cursor o modificar el icono de la aplicación. En el AWT, todos los Contenedores son instancias de la clase Container o uno de sus subtipos.

152

Page 153: Programacion en Java

Los Componentes deben circunscribirse dentro del Contenedor que los contiene. Esto hace que el anidamiento de Componentes (incluyendo Contenedores) en Contenedores crean árboles de elementos, comenzando con un Contenedor en la raiz del árbol y expandiéndolo en sus ramas.

7. GRAFICOS

7.1 Objetos Gráficos

En páginas anteriores ya se ha mostrado cómo escribir applets, cómo lanzarlos y los fundamentos básicos de la presentación de información sobre ellos. Ahora, pues, querremos hacer cosas más interesantes que mostrar texto; ya que cualquier página HTML puede mostrar texto. Para ello, Java proporciona la clase Graphics, que permite mostrar texto a través del método drawString(), pero también tiene muchos otros métodos de dibujo. Para cualquier programador, es esencial el entendimiento de la clase Graphics, antes de adentrarse en el dibujo de cualquier cosa en Java.

Esta clase proporciona el entorno de trabajo para cualquier operación gráfica que se realice dentro del AWT. Juega dos importantes papeles: por un lado, es el contexto gráfico, es decir, contiene la información que va a afectar a todas las operaciones gráficas, incluyendo los colores de fondo y texto, la fuente de caracteres, la localización y dimensiones del rectángulo en que se va a pintar, e incluso dispone de información sobre el eventual destino de las operaciones gráficas (pantalla o imagen). Por otro lado, la clase Graphics proporciona métodos que permiten el dibujo de primitivas, figuras y la manipulación de fonts de caracteres y colores. También hay clases para la manipulación de imágenes, doble-buffering, etc.

Para poder pintar, un programa necesita un contexto gráfico válido, representado por una instancia de la clase Graphics. Pero, como esta clase es abstracta, no se puede instanciar directamente; así que debemos crear un componente y pasarlo al programa como un argumento a los métodos paint() o update().

Los dos métodos anteriores, paint() y update(), junto con el método repaint() son los que están involucrados en la presentación de gráficos en pantalla. El AWT, para reducir el tiempo que necesitan estos métodos para realizar el repintado en pantalla de gráficos, tiene dos axiomas:

• Primero, el AWT repinta solamente aquellos Componentes que necesitan ser repintados, bien porque estuviesen cubiertos por otra ventana o porque se pida su repintado directamente

• Segundo, si un Componente estaba tapado y se destapa, el AWT repinta solamente la porción del Componente que estaba oculta

En la ejecución del applet que aparece a continuación, EjemploGraf.java, podemos observar como se realiza este proceso. Ignorar la zona de texto de la parte superior del applet de momento, y centrar la mirada en la parte coloreada. Utilizando otra ventana, tapar y destapar parte de la zona que ocupa el applet. Se observará que solamente el trozo de applet que estaba cubierto es el que se repinta. Yendo un poco más allá, solamente aquellos componentes que estén ocultos y se vuelvan a ver serán los que se repinten, sin tener en cuenta su posición dentro de la jerarquía de componentes.

La pantalla en Java se incrementa de izquierda a derecha y de arriba hacia abajo, tal como muestra la figura:

153

Page 154: Programacion en Java

Los pixels de la pantalla son pues: posición 0 + ancho de la pantalla - 1.

En los textos, el punto de inserción se encuentra en la línea base de la primera letra.

7.2 Métodos para Dibujos

Vamos a presentar métodos para dibujar varias figuras geométricas. Como estos métodos funcionan solamente cuando son invocados por una instancia válida de la clase Graphics, su ámbito de aplicación se restringe a los componentes que se utilicen en los métodos paint() y update(). Normalmente los métodos de dibujo de primitivas gráficas funcionan por pares: un método pinta la figura normal y el otro pinta la figura rellena.

drawLine( x1,y1,x2,y2 )

drawRect( x,y,ancho,alto )

fillRect( x,y,ancho,alto )

clearRect( x,y,ancho.alto )

drawRoundRect( x,y,ancho,alto,anchoArco,altoArco )

fillRoundRect( x,y,ancho,alto,anchoArco,altoArco )

draw3DRect( x,y,ancho,alto,boolean elevado )

fill3DRect( x,y,ancho,alto,boolean elevado )

drawOval( x,y,ancho,alto )

fillOval( x,y,ancho,alto )

drawArc( x,y,ancho,alto,anguloInicio,anguloArco )

fillArc( x,y,ancho,alto,anguloInicio,anguloArco )

drawPolygon( int[] puntosX,int[] puntosY[],numPuntos )

fillPolygon( int[] puntosX,int[] puntosY[],numPuntos )

154

Page 155: Programacion en Java

drawString( string s,x,y )

drawChars( char data[],offset,longitud,x,y )

drawBytes( byte data[],offset,longitud,x,y )

copyArea( xSrc,ySrc,ancho,alto,xDest,yDest )

7.3 Métodos para Imagenes

Los objetos Graphics pueden mostrar imágenes a través del método:

drawImage( Image img,int x,int y,ImageObserver observador );

Hay que tener en cuenta que el método drawImage() necesita un objeto Image y un objeto ImageObserver. Podemos cargar una imagen desde un fichero de dibujo (actualmente sólo se soportan formatos GIF y JPEG) con el método getImage():

Image img = getImage( getDocumentBase(),"fichero.gif" );

La forma de invocar al método getImage() es indicando un URL donde se encuentre el fichero que contiene la imagen que queremos presentar y el nombre de ese fichero:

getImage( URL directorioImagen,String ficheroImagen );

Un URL común para el método getImage() es el directorio donde está el fichero HTML. Se puede acceder a esa localización a través del método getDocumentBase() de la clase Applet, como ya se ha indicado.

Normalmente, se realiza el getImage() en el método init() del applet y se muestra la imagen cargada en el método paint(), tal como se muestra en el ejemplo siguiente:

public void init() {

img = getImage( getDocumentBase(),"pepe.gif" );

}

public void paint( Graphics g ) {

g.drawImage( img,x,y,this );

}

En el applet Imagen.java, podemos ver el ejemplo completo. Su ponemos en él la existencia del fichero "Imagenes/pepe.gif":

import java.awt.*;

import sun.awt.image.URLImageSource;

155

Page 156: Programacion en Java

import java.applet.Applet;

public class Imagen extends Applet {

Imagen pepe;

public void init() {

pepe = getImage( getDocumentBase(),"Imagenes/pepe.gif" );

}

public void paint( Graphics g ) {

g.drawString( pepe,25,25,this );

}

}

7.4 Sonido en Java

Java también posee métodos predefinidos para reproducir sonido. El ordenador remoto no necesita tener un reproductor de audio; Java realizará la reproducción (evidentemente, el ordenador remoto, en donde se ejecuta el applet, necesitará disponer de hardware de sonido).

Reproducción de sonido

La forma más fácil de reproducir sonido es a través del método play():

play( URL directorioSonido,String ficheroSonido );

o, simplemente:

play( URL unURLdeSonido );

Un URL común para el método play() es el directorio donde está el fichero HTML. Se puede acceder a esa localización a través del método getDocumentBase() de la clase Applet:

play( getDocumentBase(),"sonido.au" );

para que esto funcione, el fichero de la clase y el fichero sonido.au deberían estar en el mismo directorio.

Reproducción Repetitiva

Se puede manejar el sonido como si de imágenes se tratara. Se pueden cargar y reproducir más tarde.

Para cargar un clip de sonido, se utiliza el método getAudioClip():

156

Page 157: Programacion en Java

AudoClip sonido;

sonido = getAudioClip( getDocumentBase(),"risas.au" );

Una vez que se carga el clip de sonido, se pueden utilizar tres métodos:

sonido.play();

para reproducir el clip de sonido.

sonido.loop();

para iniciar la reproducción del clip de sonido y que entre en un blucle de reproducción, es decir, en una repetición automática del clip.

sonido.stop();

para detener el clip de sonido que se encuentre en ese instante en reproducción.

7.5 Entrada por Ratón

Una de las características más útiles que ofrece Java es el soporte directo de la interactividad. La aplicación puede reaccionar a los cambios producidos en el ratón, por ejemplo, sin necesidad de escribir ninguna línea de código para su control, solamente indicando qué se quiere hacer cuando el ratón haga algo.

El evento más común en el ratón es el click. Este evento es gobernado por dos métodos: mouseDown() (botón pulsado) y mouseUp() (botón soltado). Ambos métodos son parte de la clase Applet, pero se necesita definir sus acciones asociadas, de la misma forma que se realiza con init() o con paint().

8. EXCEPCIONES EN JAVA

8.1 Manejo de Excepciones

Vamos a mostrar como se utilizan las excepciones, reconvirtiendo nuestro applet de saludo a partir de la versión iterativa de HolaIte.java:

import java.awt.*;

import java.applet.Applet;

public class HolaIte extends Applet {

private int i = 0;

private String Saludos[] = {

"Hola Mundo!",

"HOLA Mundo!",

157

Page 158: Programacion en Java

"HOLA MUNDO!!"

};

public void paint( Graphics g ) {

g.drawString( Saludos[i],25,25 );

i++;

}

}

Normalmente, un programa termina con un mensaje de error cuando se lanza una excepción. Sin embargo, Java tiene mecanismos para excepciones que permiten ver qué excepción se ha producido e intentar recuperarse de ella.

Vamos a reescribir el método paint() de nuestra versión iterativa del saludo:

public void paint( Graphics g ) {

try {

g.drawString( Saludos[i],25,25 );

} catch( ArrayIndexOutOfBoundsException e ) {

g.drawString( "Saludos desbordado",25,25 );

} catch( Exception e ) {

// Cualquier otra excepción

System.out.println( e.toString() );

} finally {

System.out.println( "Esto se imprime siempre!" );

}

i++;

}

La palabra clave finally define un bloque de código que se quiere que sea ejecutado siempre, de acuerdo a si se capturó la excepción o no. En el ejemplo anterior, la salida en la consola, con i=4 sería:

Saludos desbordado

158

Page 159: Programacion en Java

¡Esto se imprime siempre!

8.2 Generar Excepciones en Java

Cuando se produce un error se debería generar, o lanzar, una excepción. Para que un método en Java, pueda lanzar excepciones, hay que indicarlo expresamente.

void MetodoAsesino() throws NullPointerException,CaidaException

Se pueden definir excepciones propias, no hay por qué limitarse a las predefinidas; bastará con extender la clase Exception y proporcionar la funcionalidad extra que requiera el tratamiento de esa excepción.

También pueden producirse excepciones no de forma explícita como en el caso anterior, sino de forma implícita cuando se realiza alguna acción ilegal o no válida.

Las excepciones, pues, pueden originarse de dos modos: el programa hace algo ilegal (caso normal), o el programa explícitamente genera una excepción ejecutando la sentencia throw (caso menos normal). La sentencia throw tiene la siguiente forma:

throw ObtejoExcepction;

El objeto ObjetoException es un objeto de una clase que extiende la clase Exception. El siguiente código de ejemplo origina una excepción de división por cero:

class melon {

public static void main( String[] a ) {

int i=0, j=0, k;

k = i/j; // Origina un error de division-by-zero

}

}

Si compilamos y ejecutamos esta aplicación Java, obtendremos la siguiente salida por pantalla:

> javac melon.java

> java melon

java.lang.ArithmeticException: / by zero

at melon.main(melon.java:5)

Las excepciones predefinidas, como ArithmeticException, se conocen como excepciones runtime.

Actualmente, como todas las excepciones son eventos runtime, sería mejor llamarlas excepciones irrecuperables. Esto contrasta con las excepciones que generamos explícitamente, que suelen ser mucho menos severas y en la mayoría de los casos podemos recuperarnos de ellas. Por ejemplo,

159

Page 160: Programacion en Java

si un fichero no puede abrirse, preguntamos al usuario que nos indique otro fichero; o si una estructura de datos se encuentra completa, podremos sobreescribir algún elemento que ya no se necesite.

8.3 Excepciones Predefinidas

Las excepciones predefinidas y su jerarquía de clases es la que se muestra en la figura:

160

Page 161: Programacion en Java

Las siguientes son las excepciones predefinidas más frecuentes que se pueden encontrar:

ArithmeticException

Las excepciones aritméticas son típicamente el resultado de una división por 0:

int i = 12 / 0;

NullPointerException

Se produce cuando se intenta acceder a una variable o método antes de ser definido:

class Hola extends Applet {

Image img;

paint( Graphics g ) {

g.drawImage( img,25,25,this );

}

}

IncompatibleClassChangeException

El intento de cambiar una clase afectada por referencias en otros objetos, específicamente cuando esos objetos todavía no han sido recompilados.

ClassCastException

El intento de convertir un objeto a otra clase que no es válida.

y = (Prueba)x; // donde

x no es de tipo Prueba

NegativeArraySizeException

Puede ocurrir si hay un error aritmético al intentar cambiar el tamaño de un array.

OutOfMemoryException

¡No debería producirse nunca! El intento de crear un objeto con el operador new ha fallado por falta de memoria. Y siempre tendría que haber memoria suficiente porque el garbage collector se encarga de proporcionarla al ir liberando objetos que no se usan y devolviendo memoria al sistema.

NoClassDefFoundException

Se referenció una clase que el sistema es incapaz de encontrar.

161

Page 162: Programacion en Java

ArrayIndexOutOfBoundsException

Es la excepción que más frecuentemente se produce. Se genera al intentar acceder a un elemento de un array más allá de los límites definidos inicialmente para ese array.

UnsatisfiedLinkException

Se hizo el intento de acceder a un método nativo que no existe. Aquí no existe un método a.kk

class A {

native void kk();

}

y se llama a a.kk(), cuando debería llamar a A.kk().

InternalException

Este error se reserva para eventos que no deberían ocurrir. Por definición, el usuario nunca debería ver este error y esta excepción no debería lanzarse.

8.4 Crear Excepciones Propias

También podemos lanzar nuestras propias excepciones, extendiendo la clase System.exception.

Por ejemplo, consideremos un programa cliente/servidor. El código cliente se intenta conectar al servidor, y durante 5 segundos se espera a que conteste el servidor. Si el servidor no responde, el servidor lanzaría la excepción de time-out:

Cualquier método que lance una excepción también debe capturarla, o declararla como parte de la interface del método. Cabe preguntarse entonces, el porqué de lanzar una excepción si hay que capturarla en el mismo método. La respuesta es que las excepciones no simplifican el trabajo del control de errores. Tienen la ventaja de que se puede tener muy localizado el control de errores y no tenemos que controlar millones de valores de retorno, pero no van más allá.

8.5 Capturar Excepciones

Las excepciones lanzadas por un método que pueda hacerlo deben recoger en bloque try/catch o

try/finally.

try

Es el bloque de código donde se prevé que se genere una excepción. Es como si dijésemos "intenta estas sentencias y mira a ver si se produce una excepción". El bloque try tiene que ir seguido, al menos, por una cláusula catch o una cláusula finally

catch

Es el código que se ejecuta cuando se produce la excepción. Es como si dijésemos "controlo cualquier excepción que coincida con mi argumento". En este bloque tendremos que asegurarnos

162

Page 163: Programacion en Java

de colocar código que no genere excepciones. Se pueden colocar sentencias catch sucesivas, cada una controlando una excepción diferente. No debería intentarse capturar todas las excepciones con una sola cláusula. Esto representaría un uso demasiado general, podrían llegar muchas más excepciones de las esperadas. En este caso es mejor dejar que la excepción se propague hacia arriba y dar un mensaje de error al usuario.

Se pueden controlar grupos de excepciones, es decir, que se pueden controlar, a través del argumento, excepciones semejantes.

La cláusula catch comprueba los argumentos en el mismo orden en que aparezcan en el programa.

Si hay alguno que coincida, se ejecuta el bloque. El operador instanceof se utiliza para identificar exactamente cual ha sido la identidad de la excepción.

finally

Es el bloque de código que se ejecuta siempre, haya o no excepción. Hay una cierta controversia entre su utilidad, pero, por ejemplo, podría servir para hacer un log o un seguimiento de lo que está pasando, porque como se ejecuta siempre puede dejarnos grabado si se producen excepciones y nos hemos recuperado de ellas o no.

Este bloque finally puede ser útil cuando no hay ninguna excepción. Es un trozo de código que se ejecuta independientemente de lo que se haga en el bloque try.

Cuando vamos a tratar una excepción, se nos plantea el problema de qué acciones vamos a tomar. En la mayoría de los casos, bastará con presentar una indicación de error al usuario y un mensaje avisándolo de que se ha producido un error y que decida si quiere o no continuar con la ejecución del programa.

8.6 Propagacion de Excepciones

La cláusula catch comprueba los argumentos en el mismo orden en que aparezcan en el programa.

Si hay alguno que coincida, se ejecuta el bloque y sigue el flujo de control por el bloque finally (si lo hay) y concluye el control de la excepción.

Si ninguna de las cláusulas catch coincide con la excepción que se ha producido, entonces se ejecutará el código de la cláusula finally (en caso de que la haya). Lo que ocurre en este caso, es exactamente lo mismo que si la sentencia que lanza la excepción no se encontrase encerrada en el bloque try.

El flujo de control abandona este método y retorna prematuramente al método que lo llamó. Si la llamada estaba dentro del ámbito de una sentencia try, entonces se vuelve a intentar el control de la excepción, y así continuamente.

Veamos lo que sucede cuando una excepción no es tratada en la rutina en donde se produce. El sistema Java busca un bloque try..catch más allá de la llamada, pero dentro del método que lo trajo aquí. Si la excepción se propaga de todas formas hasta lo alto de la pila de llamadas sin encontrar un controlador específico para la excepción, entonces la ejecución se detendrá dando un mensaje. Es decir, podemos suponer que Java nos está proporcionando un bloque catch por defecto, que imprime un mensaje de error y sale.

163

Page 164: Programacion en Java

No hay ninguna sobrecarga en el sistema por incorporar sentencias try al código. La sobrecarga se produce cuando se genera la excepción.

Hemos dicho ya que un método debe capturar las excepciones que genera, o en todo caso, declararlas como parte de su llamada, indicando a todo el mundo que es capaz de generar excepciones. Esto debe ser así para que cualquiera que escriba una llamada a ese método esté avisado de que le puede llegar una excepción, en lugar del valor de retorno normal. Esto permite al programador que llama a ese método, elegir entre controlar la excepción o propagarla hacia arriba en la pila de llamadas.

9. Threads y Multithreading

Considerando el entorno multithread, cada thread (hilo, flujo de control del programa) representa un proceso individual ejecutándose en un sistema. A veces se les llama procesos ligeros o contextos de ejecución. Típicamente, cada thread controla un único aspecto dentro de un programa, como puede ser supervisar la entrada en un determinado periférico o controlar toda la entrada/salida del disco. Todos los threads comparten los mismos recursos, al contrario que los procesos en donde cada uno tiene su propia copia de código y datos (separados unos de otros). Gráficamente, los threads se parecen en su funcionamiento a lo que muestra la figura siguiente:

9.1 Flujo en Programas

Programas de flujo único

Un programa de flujo único o mono-hilvanado (single-thread) utiliza un único flujo de control (thread) para controlar su ejecución. Muchos programas no necesitan la potencia o utilidad de múltiples flujos de control. Sin necesidad de especificar explícitamente que se quiere un único flujo de control, muchos de los applets y aplicaciones son de flujo único. Programas de flujo múltiple

En nuestra aplicación de saludo, no vemos el thread que ejecuta nuestro programa. Sin embargo, Java posibilita la creación y control de threads explícitamente. La utilización de threads en Java, permite una enorme flexibilidad a los programadores a la hora de plantearse el desarrollo de aplicaciones. La simplicidad para crear, configurar y ejecutar threads, permite que se puedan implementar muy poderosas y portables aplicaciones/applets que no se puede con otros lenguajes de tercera generación. En un lenguaje orientado a Internet como es Java, esta herramienta es vital.

Si se ha utilizado un navegador con soporte Java, ya se habrá visto el uso de múltiples threads en Java. Habrá observado que dos applet se pueden ejecutar al mismo tiempo, o que puede desplazarla página del navegador mientras el applet continúa ejecutándose. Esto no significa que el applet utilice múltiples threads, sino que el navegador es multithreaded.

164

Page 165: Programacion en Java

Las aplicaciones (y applets) multithreaded utilizan muchos contextos de ejecución para cumplir su trabajo. Hacen uso del hecho de que muchas tareas contienen subtareas distintas e independientes. Se puede utilizar un thread para cada subtarea.

Mientras que los programas de flujo único pueden realizar su tarea ejecutando las subtareas secuencialmente, un programa multithreaded permite que cada thread comience y termine tan pronto como sea posible. Este comportamiento presenta una mejor respuesta a la entrada en tiempo real.

9.2 Creacion y Control de threads

Creación de un Thread

Hay dos modos de conseguir threads en Java. Una es implementando la interface Runnable, la otra es extender la clase Thread.

La implementación de la interface Runnable es la forma habitual de crear threads. Las interfaces proporcionan al programador una forma de agrupar el trabajo de infraestructura de una clase. Se utilizan para diseñar los requerimientos comunes al conjunto de clases a implementar. La interface define el trabajo y la clase, o clases, que implementan la interface realizan ese trabajo. Los diferentes grupos de clases que implementen la interface tendrán que seguir las mismas reglas de funcionamiento.

Hay una cuantas diferencias entre interface y clase. Primero, una interface solamente puede contener métodos abstractos y/o variables estáticas y finales (constantes). Las clases, por otro lado, pueden implementar métodos y contener variables que no sean constantes. Segundo, una interface no puede implementar cualquier método. Una clase que implemente una interface debe implementar todos los métodos definidos en esa interface. Una interface tiene la posibilidad de poder extenderse de otras interfaces y, al contrario que las clases, puede extenderse de múltiples interfaces. Además, una interface no puede ser instanciada con el operador new; por ejemplo, la siguiente sentencia no está permitida:

Runnable a = new Runnable(); // No se permite

El primer método de crear un thread es simplemente extender la clase Thread:

class MiThread extends Thread {

public void run() {

. . .

}

El ejemplo anterior crea una nueva clase MiThread que extiende la clase Thread y sobrecarga el método Thread.run() por su propia implementación. El método run() es donde se realizará todo el trabajo de la clase. Extendiendo la clase Thread, se pueden heredar los métodos y variables de la clase padre. En este caso, solamente se puede extender o derivar una vez de la clase padre. Esta limitación de Java puede ser superada a través de la implementación de Runnable:

public class MiThread implements Runnable {

165

Page 166: Programacion en Java

Thread t;

public void run() {

// Ejecución del thread una vez creado

}

}

En este caso necesitamos crear una instancia de Thread antes de que el sistema pueda ejecutar el proceso como un thread. Además, el método abstracto run() está definido en la interface Runnable tiene que ser implementado. La única diferencia entre los dos métodos es que este último es mucho más flexible. En el ejemplo anterior, todavía tenemos oportunidad de extender la clase MiThread, si fuese necesario. La mayoría de las clases creadas que necesiten ejecutarse como un thread , implementarán la interface Runnable, ya que probablemente extenderán alguna de su funcionalidad a otras clases.

No pensar que la interface Runnable está haciendo alguna cosa cuando la tarea se está ejecutando. Solamente contiene métodos abstractos, con lo cual es una clase para dar idea sobre el diseño de la clase Thread. De hecho, si vemos los fuentes de Java, podremos comprobar que solamente contiene un método abstracto:

package java.lang;

public interface Runnable {

public abstract void run() ;

}

Y esto es todo lo que hay sobre la interface Runnable. Como se ve, una interface sólo proporciona un diseño para las clases que vayan a ser implementadas. En el caso de Runnable, fuerza a la definición del método run(), por lo tanto, la mayor parte del trabajo se hace en la clase Thread. Un vistazo un poco más profundo a la definición de la clase Thread nos da idea de lo que realmente está pasando:

public class Thread implements Runnable {

...

public void run() {

if( tarea != null )

tarea.run() ;

}

}

...

166

Page 167: Programacion en Java

}

De este trocito de código se desprende que la clase Thread también implemente la interface Runnable. tarea.run() se asegura de que la clase con que trabaja (la clase que va a ejecutarse como un thread) no sea nula y ejecuta el método run() de esa clase. Cuando esto suceda, el método run() de la clase hará que corra como un thread.

Arranque de un Thread

Las aplicaciones ejecutan main() tras arrancar. Esta es la razón de que main() sea el lugar natural para crear y arrancar otros threads. La línea de código:

t1 = new TestTh( "Thread 1",(int)(Math.random()*2000) );

crea un nuevo thread. Los dos argumentos pasados representan el nombre del thread y el tiempo que queremos que espere antes de imprimir el mensaje.

Al tener control directo sobre los threads, tenemos que arrancarlos explícitamente. En nuestro ejemplo con:

t1.start();

start(), en realidad es un método oculto en el thread que llama al método run().

Manipulación de un Thread

Si todo fue bien en la creación del thread, t1 debería contener un thread válido, que controlaremos en el método run().

Una vez dentro de run(), podemos comenzar las sentencias de ejecución como en otros programas. run() sirve como rutina main() para los threads; cuando run() termina, también lo hace el thread. Todo lo que queramos que haga el thread ha de estar dentro de run(), por eso cuando decimos que un método es Runnable, nos obliga a escribir un método run().

En este ejemplo, intentamos inmediatamente esperar durante una cantidad de tiempo aleatoria (pasada a través del constructor):

sleep( retardo );

El método sleep() simplemente le dice al thread que duerma durante los milisegundos especificados. Se debería utilizar sleep() cuando se pretenda retrasar la ejecución del thread. sleep() no consume recursos del sistema mientras el thread duerme. De esta forma otros threads pueden seguir funcionando. Una vez hecho el retardo, se imprime el mensaje "Hola Mundo!" con el nombre del thread y el retardo.

Suspensión de un Thread

Puede resultar útil suspender la ejecución de un thread sin marcar un límite de tiempo. Si, por ejemplo, está construyendo un applet con un thread de animación, querrá permitir al usuario la opción de detener la animación hasta que quiera continuar. No se trata de terminar la animación, sino desactivarla. Para este tipo de control de thread se puede utilizar el método suspend().

167

Page 168: Programacion en Java

t1.suspend();

Este método no detiene la ejecución permanentemente. El thread es suspendido indefinidamente y para volver a activarlo de nuevo necesitamos realizar una invocación al método resume():

t1.resume();

Parada de un Thread

El último elemento de control que se necesita sobre threads es el método stop(). Se utiliza para terminar la ejecución de un thread:

t1.stop();

Esta llamada no destruye el thread, sino que detiene su ejecución. La ejecución no se puede reanudar ya con t1.start(). Cuando se desasignen las variables que se usan en el thread, el objeto thread (creado con new) quedará marcado para eliminarlo y el garbage collector se encargará de liberar la memoria que utilizaba.

En nuestro ejemplo, no necesitamos detener explícitamente el thread. Simplemente se le deja terminar. Los programas más complejos necesitarán un control sobre cada uno de los threads que lancen, el método stop() puede utilizarse en esas situaciones.

Si se necesita, se puede comprobar si un thread está vivo o no; considerando vivo un thread que ha comenzado y no ha sido detenido.

t1.isAlive();

Este método devolverá true en caso de que el thread t1 esté vivo, es decir, ya se haya llamado a su método run() y no haya sido parado con un stop() ni haya terminado el método run() en su ejecución.

9.3 Estados de un thread

Durante el ciclo de vida de un thread, éste se puede encontrar en diferentes estados. La figura siguiente muestra estos estados y los métodos que provocan el paso de un estado a otro. Este diagrama no es una máquina de estados finita, pero es lo que más se aproxima al funcionamiento real de un thread .

168

Page 169: Programacion en Java

Nuevo Thread

Cuando un thread está en este estado, es simplemente un objeto Thread vacío. El sistema no ha destinado ningún recurso para él. Desde este estado solamente puede arrancarse llamando al método start(), o detenerse definitivamente, llamando al método stop(); la llamada a cualquier otro método carece de sentido y lo único que provocará será la generación de una excepción de tipo IllegalThreadStateException.

Ejecutable

La llamada al método start() creará los recursos del sistema necesarios para que el thread puede ejecutarse, lo incorpora a la lista de procesos disponibles para ejecución del sistema y llama al método run() del thread. En este momento nos encontramos en el estado "Ejecutable" del diagrama. Y este estado es Ejecutable y no En Ejecución, porque cuando el thread está aquí no esta corriendo. Muchos ordenadores tienen solamente un procesador lo que hace imposible que todos los threads estén corriendo al mismo tiempo. Java implementa un tipo de scheduling o lista de procesos, que permite que el procesador sea compartido entre todos los procesos o threads que se encuentran en la lista. Sin embargo, para nuestros propósitos, y en la mayoría de los casos, se puede considerar que este estado es realmente un estado "En Ejecución", porque la impresión que produce ante nosotros es que todos los procesos se ejecutan al mismo tiempo.

Cuando el thread se encuentra en este estado, todas las instrucciones de código que se encuentren dentro del bloque declarado para el método run(), se ejecutarán secuencialmente.

Parado

El thread entra en estado "Parado" cuando alguien llama al método suspend(), cuando se llama al método sleep(), cuando el thread está bloqueado en un proceso de entrada/salida o cuando el thread utiliza su método wait() para esperar a que se cumpla una determinada condición. Cuando ocurra cualquiera de las cuatro cosas anteriores, el thread estará Parado.

Para cada una de los cuatro modos de entrada en estado Parado, hay una forma específica de volver a estado Ejecutable. Cada forma de recuperar ese estado es exclusiva; por ejemplo, si el thread ha sido puesto a dormir, una vez transcurridos los milisegundos que se especifiquen, él solo se despierta y vuelve a estar en estado Ejecutable. Llamar al método resume() mientras esté el thread durmiendo no serviría para nada.

169

Page 170: Programacion en Java

Los métodos de recuperación del estado Ejecutable, en función de la forma de llegar al estado Parado del thread, son los siguientes:

• Si un thread está dormido, pasado el lapso de tiempo • Si un thread está suspendido, luego de una llamada al método resume() • Si un thread está bloqueado en una entrada/salida, una vez que el comando E/S concluya

su ejecución • Si un thread está esperando por una condición, cada vez que la variable que controla esa

condición varíe debe llamarse a notify() o notifyAll()

Muerto

Un thread se puede morir de dos formas: por causas naturales o porque lo maten (con stop()). Un thread muere normalmente cuando concluye de forma habitual su método run(). Por ejemplo, en el siguiente trozo de código, el bucle while es un bucle finito -realiza la iteración 20 veces y termina-:

El método isAlive()

La interface de programación de la clase Thread incluye el método isAlive(), que devuelve true si el thread ha sido arrancado (con start()) y no ha sido detenido (con stop()). Por ello, si el método isAlive() devuelve false, sabemos que estamos ante un "Nuevo Thread" o ante un thread "Muerto". Si nos devuelve true, sabemos que el thread se encuentra en estado "Ejecutable" o "Parado". No se puede diferenciar entre "Nuevo Thread" y "Muerto", ni entre un thread "Ejecutable" o "Parado".

9.4 Comunicacion entre threads

Otra clave para el éxito y la ventaja de la utilización de múltiples threads en una aplicación, o aplicación multithreaded, es que pueden comunicarse entre sí. Se pueden diseñar threads para utilizar objetos comunes, que cada thread puede manipular independientemente de los otros threads.

El ejemplo clásico de comunicación de threads es un modelo productor/consumidor. Un thread produce una salida, que otro thread usa (consume), sea lo que sea esa salida. Vamos entonces a crear un productor, que será un thread que irá sacando caracteres por su salida; crearemos también un consumidor que ira recogiendo los caracteres que vaya sacando el productor y un monitor que controlará el proceso de sincronización entre los threads.

Funcionará como una tubería, insertando el productor caracteres en un extremos y leyéndolos el consumidor en el otro, con el monitor siendo la propia tubería.

11. METODOS NATIVOS

170

Page 171: Programacion en Java

Un método nativo es un método Java (una instancia de un objeto o una clase) cuya implementación se ha realizado en otro lenguaje de programación, por ejemplo, C. Vamos a ver cómo se integran métodos nativos en clases Java. Actualmente, el lenguaje Java solamente proporciona mecanismos para integrar código C en programas Java.

Veamos pues los pasos necesarios para mezclar código nativo C y programas Java. Recurriremos (¡Cómo no!) a nuestro saludo; en este caso, el programa HolaMundo tiene dos clases Java: la primera implementa el método main() y la segunda, HolaMundo, tiene un método nativo que presenta el mensaje de saludo. La implementación de este segundo método la realizaremos en C.

10.1 Escribir Código Java

En primer lugar, debemos crear una clase Java, HolaMundo, que declare un método nativo. También debemos crear el programa principal que cree el objeto HolaMundo y llame al método nativo.

Las siguientes líneas de código definen la clase HolaMundo, que consta de un método y un segmento estático de código:

class HolaMundo {

public native void presentaSaludo();

static {

System.loadLibrary( "hola" );

}

}

Podemos decir que la implementación del método presentaSaludo() de la clase HolaMundo está escrito en otro lenguaje, porque la palabra reservada native aparece como parte de la definición del método. Esta definición, proporciona solamente la definición para presentaSaludo() y no porporciona ninguna implementación para él. La implementación la proporcionaremos desde un fichero fuente separado, escrito en lenguaje C.

La definición para presentaSaludo() también indica que el método es un método público, no acepta argumentos y no devuelve ningún valor. Al igual que cualquier otro método, los métodos nativos deben estar definidos dentro de una clase Java.

El código C que implementa el método presentaSaludo() debe ser compilado en una librería dinámica y cargado en la clase Java que lo necesite. Esta carga, mapea la implementación del método nativo sobre su definición.

El siguiente bloque de código carga la librería dinámica, en este caso hola. El sistema Java ejecutará un bloque de código estático de la clase cuando la cargue.

Todo el código anterior forma parte del fichero HolaMundo.java, que contiene la clase HolaMundo. En un fichero separado, Main.java, vamos a crear una aplicación Java que instancie a la clase HolaMundo y llame al método nativo presentaSaludo().

class Main {

171

Page 172: Programacion en Java

public static void main( String args[] ) {

new HolaMundo().presentaSaludo();

}

}

Como se puede observar, llamamos al método nativo del mismo modo que a cualquier otro método Java; añadimos el nombre del método al final del nombre del objeto con un punto ("."). El conjunto de paréntesis que sigue al nombre del método encierra los argumentos que se le pasen. En este caso, el método presentaSaludo() no recibe ningún tipo de argumento.

10.2 Compilar el Código Java

Utilizaremos ahora el compilador javac para compilar el código Java que hemos desarrollado. Compilaremos los dos ficheros fuentes de código Java que hemos creado, tecleando los siguientes comandos:

> javac HolaMundo.java

> javac Main.java

10.3 Crear el Fichero de Cabecera

Ahora debemos utilizar la aplicación javah para conseguir el fichero de cabecera .h. El fichero de cabecera define una estructura que representa la clase HolaMundo sobre código C y proporciona la definición de una función C para la implementación del método nativo presentaSaludo() definido en ese clase.

Ejecutamos javah sobre la clase HolaMundo, con el siguiente comando:

> javah HolaMundo

Por defecto, javah creará el nuevo fichero .h en el mismo directorio en que se encuentra el fichero .class, obtenido al compilar con javac el código fuente Java correspondiente a la clase. El fichero que creará, será un fichero de cabecera del mismo nombre que la clase y con extensión .h. Por ejemplo, el comando anterior habrá creado el fichero HolaMundo.h, cuyo contenido será el siguiente:

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <native.h>

/* Header for class HolaMundo */

#ifndef _Included_HolaMundo

#define _Included_HolaMundo

typedef struct ClassHolaMundo {

172

Page 173: Programacion en Java

char PAD; /* ANSI C requires structures to have a least one member */

} ClassHolaMundo;

HandleTo(HolaMundo);

#ifdef __cplusplus

extern "C" {

#endif

__declspec(dllexport) void HolaMundo_presentaSaludo(struct HHolaMundo *);

#ifdef __cplusplus

}

#endif

#endif

Este fichero de cabecera contiene la definición de una estructura llamada ClassHolaMundo. Los miembros de esta estructura son paralelos a los miembros de la clase Java correspondiente; es decir, los campos en la estructura corresponden a las variables de la clase. Pero como HolaMundo no tiene ninguna variable, la estructura se encuentra vacía. Se pueden utilizar los miembros de la estructura para referenciar a variables instanciadas de la clase desde las funciones C. Además de la estructura C similar a la clase Java, vemos que la llamada de la función C está declarada como:

extern void HolaMundo_presentaSaludo( struct HHolaMundo *);

Esta es la definición de la función C que deberemos escribir para implementar el método nativo presentaSaludo() de la clase HolaMundo. Debemos utilizar esa definición cuando lo implementemos. Si HolaMundo llamase a otros métodos nativos, las definiciones de las funciones también aparecerían aquí.

El nombre de la función C que implementa el método nativo está derivado del nombre del paquete, el nombre de la clase y el nombre del método nativo. Así, el método nativo presentaSaludo() dentro de la clase HolaMundo es HolaMundo_presentaSaludo(). En este ejemplo, no hay nombre de paquete porque HolaMundo se considera englobado dentro del paquete por defecto.

La función C acepta un parámetro, aunque el método nativo definido en la clase Java no acepte ninguno. Se puede pensar en este parámetro como si fuese la variable this de C++. En nuestro caso, ignoramos el parámetro this.

10.4 Crear el Fichero de stubs

Volvemos a utilizar la aplicación javah para crear el fichero de stubs, que contiene todas las declaraciones de métodos, con sus llamadas y argumentos, listos para que nosotros rellenemos el cuerpo de los métodos con los algoritmos que necesitemos implementar. Proporciona la unión entre la clase Java y su estructura C paralela.

173

Page 174: Programacion en Java

Para generar este fichero, debemos indicar el parámetro .stubs al ejecutar la aplicación javah sobre la clase HolaMundo, de la siguiente forma:

> javah -stubs HolaMundo

Del mismo modo que se generaba el fichero .h; el nombre del fichero de stubs será el nombre de la clase con la extensión .c. En nuestro ejemplo, será HolaMundo.c, y su contenido será el siguiente:

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <StubPreamble.h>

/* Stubs for class HolaMundo */

/* SYMBOL: "HolaMundo/presentaSaludo()V",

Java_HolaMundo_presentaSaludo_stub */

__declspec(dllexport) stack_item

*Java_HolaMundo_presentaSaludo_stub(stack_item *_P_,struct execenv *_EE_) {

extern void HolaMundo_presentaSaludo(void *);

(void) HolaMundo_presentaSaludo(_P_[0].p);

return _P_;

}

10.5 Escribir la funcion C

Escribiremos la función C para el método nativo en un fichero fuente de código C. La implementación será una función habitual C, que luego integraremos con la clase Java. La definición de la función C debe ser la misma que la que se ha generado con javah en el fichero HolaMundo.h. La implementación que proponemos la guardaremos en el fichero HolaImp.c, y contendrá las siguientes línea de código:

#include <StubPreamble.h>

#include "HolaMundo.h>

#include <stdio.h>

void HolaMundo_presentaSaludo( struct HHolaMundo *this ) {

printf( "Hola Mundo, desde el Tutorial de Java\n" );

return;

}

174

Page 175: Programacion en Java

Como se puede ver, la implementación no puede ser más sencilla: hace una llamada a la función printf() para presentar el saludo y sale.

En el código se incluyen tres ficheros de cabecera:

StubsPreamble.h

Proporciona la información para que el código C pueda interactuar con el sistema Java. Cuando se escriben métodos nativos, siempre habrá que incluir este fichero en el código fuente C.

HolaMundo.h

Es el fichero de cabecera que hemos generado para nuestra clase. Contiene la estructura C que representa la clase Java para la que estamos escribiendo el método nativo y la definición de la función para ese método nativo.

stdio.h

Es necesario incluirlo porque utilizamos la función printf() de la librería estándar de C, cuya declaración se encuentra en este fichero de cabecera.

10.6 Crear la Libreria Dinámica

Utilizaremos el compilador C para compilar el fichero .h, el fichero de stubs y el fichero fuente .c; para crear una librería dinámica. Para crearla, utilizaremos el compilador C de nuestro sistema, haciendo que los ficheros HolaMundo.c y HolaImp.c generen una librería dinámica de nombre hola, que será la que el sistema Java cargue cuando ejecute la aplicación que estamos construyendo.

Vamos a ver cómo generamos esta librería en Unix y en Windows.

Unix

Teclearemos el siguiente comando:

% cc -G HolaMundo.c HolaImp.c -o libhola.so

En caso de que no encuentre el compilador los ficheros de cabecera, se puede utilizar el flag -I para indicarle el camino de búsqueda, por ejemplo:

% cc -G -I$JAVA_HOME/include HolaMundo.c HolaImp.c -o libhola.so

donde $JAVA_HOME es el directorio donde se ha instalado la versión actual del Java Development Kit.

Windows

El comando a utilizar en este caso es el siguiente:

c:\>cl HolaMundo.c HolaImp.c -Fhola.dll -MD -LD javai.lib

175

Page 176: Programacion en Java

Este comando funciona con Microsoft Visual C++ 2.x y posteriores. Si queremos indicar al compilador donde se encuentran los ficheros de cabecera y las librerías, tendremos que fijar dos variables de entorno:

c:\>SET INCLUDE=%JAVAHOME%\include;%INCLUDE%

c:\>SET LIB=%JAVAHOME%\lib;%LIB%

donde %JAVAHOME% es el directorio donde se ha instalado la versión actual del Java Development Kit.

10.7 Ejecutar el Programa

Y, por fin, utilizaremos el intérprete de Java, java, para ejecutar el programa que hemos construido siguiendo todos los pasos anteriormente descritos. Si tecleamos el comando:

> java Main

obtendremos el resultado siguiente:

% Hola Mundo, desde el Tutorial de Java

Si no aparece este mensaje de saludo y lo que aparece en pantalla son expresiones como UnsatisfiedLinkError, es porque no tenemos fijado correctamente el camino de la librería dinámica que hemos generado. Este camino es la lista de directorios que el sistema Java utilizará para buscar las librerías que debe cargar. Debemos asegurarnos de que el directorio donde se encuentra nuestra librería hola recién creada, figura entre ellos.

Si fijamos el camino correcto y ejecutamos de nuevo el programa, veremos que ahora sí obtenemos el mensaje de saludo que esperábamos.

Con ello, hemos visto como integrar código C en programas Java. Quedan muchas cuestiones por medio, como la equivalencia de tipos entre Java y C, el paso de parámetros, el manejo de cadenas, etc. Pero eso supondría entrar en mucha más profundidad dentro de Java de la que aquí pretendemos (por ahora).

11. Entrada/Salida Estándar

Los usuarios de Unix, y aquellos familiarizados con las líneas de comandos de otros sistemas como DOS, han utilizado un tipo de entrada/salida conocida comúnmente por entrada/salida estándar. El fichero de entrada estándar (stdin) es simplemente el teclado. El fichero de salida estándar (stdout) es típicamente la pantalla (o la ventana del terminal). El fichero de salida de error estándar (stderr) también se dirige normalmente a la pantalla, pero se implementa como otro fichero de forma que se pueda distinguir entre la salida normal y (si es necesario) los mensajes de error.

11.1 La clase System

Java tiene acceso a la entrada/salida estándar a través de la clase System. En concreto, los tres ficheros que se implementan son:

Stdin

176

Page 177: Programacion en Java

System.in implementa stdin como una instancia de la clase InputStream. Con System.in, se accede a los métodos read() y skip(). El método read() permite leer un byte de la entrada. skip( long n ), salta n bytes de la entrada.

Stdout

System.out implementa stdout como una instancia de la clase PrintStream. Se pueden utilizar los métodos print() y println() con cualquier tipo básico Java como argumento.

Stderr

System.err implementa stderr de la misma forma que stdout. Como con System.out, se tiene acceso a los métodos de PrintStream.

Vamos a ver un pequeño ejemplo de entrada/salida en Java. El código siguiente, miType.java, reproduce, o funciona como la utilidad cat de Unix o type de DOS:

import java.io.*;

class miType {

public static void main( String args[] ) throws IOException {

int c;

int contador = 0;

while( (c = System.in.read() ) != '\n' )

{

contador++;

System.out.print( (char)c );

}

System.out.println(); // Línea en blanco

System.err.println( "Contados "+ contador +" bytes en total." );

}

}

11.2 Clases comunes de Entrada/Salida

Además de la entrada por teclado y salida por pantalla, se necesita entrada/salida por fichero, como son:

FileInputStream

177

Page 178: Programacion en Java

DataInputStream

FileOutputStream

DataOutputStream

También existen otras clases para aplicaciones más específicas, que no vamos a tratar, por ser de un uso muy concreto:

PipedInputStream

BufferedInputStream

PushBackInputStream

StreamTokenizer

PipedOutputStream

BufferedOutputStream

12. FICHEROS EN JAVA

Todos los lenguajes de programación tienen alguna forma de interactuar con los sistemas de ficheros locales; Java no es una excepción.

Cuando se desarrollan applets para utilizar en red, hay que tener en cuenta que la entrada/salida directa a fichero es una violación de seguridad de acceso. Muchos usuarios configurarán sus navegadores para permitir el acceso al sistema de ficheros, pero otros no.

Por otro lado, si se está desarrollando una aplicación Java para uso interno, probablemente será necesario el acceso directo a ficheros.

Antes de realizar acciones sobre un fichero, necesitamos un poco de información sobre ese fichero. La clase File proporciona muchas utilidades relacionadas con ficheros y con la obtención de información básica sobre esos ficheros.

12.1 Creación de un objeto File

Para crear un objeto File nuevo, se puede utilizar cualquiera de los tres constructores siguientes:

File miFichero;

miFichero = new File( "/etc/kk" );

o

miFichero = new File( "/etc","kk" );

o

178

Page 179: Programacion en Java

File miDirectorio = new File( "/etc" );

miFichero = new File( miDirectorio,"kk" );

El constructor utilizado depende a menudo de otros objetos File necesarios para el acceso. Por ejemplo, si sólo se utiliza un fichero en la aplicación, el primer constructor es el mejor. Si en cambio, se utilizan muchos ficheros desde un mismo directorio, el segundo o tercer constructor serán más cómodos. Y si el directorio o el fichero es una variable, el segundo constructor será el más útil.

12.2 Ficheros de Acceso Aleatorio

A menudo, no se desea leer un fichero de principio a fin; sino acceder al fichero como una base de datos, donde se salta de un registro a otro; cada uno en diferentes partes del fichero. Java proporciona una clase RandomAccessFile para este tipo de entrada/salida.

Creación de un Fichero de Acceso Aleatorio

Hay dos posibilidades para abrir un fichero de acceso aleatorio:

Con el nombre del fichero:

miRAFile = new RandomAccessFile( String nombre,String modo );

Con un objeto File:

miRAFile = new RandomAccessFile( File fichero,String modo );

El argumento modo determina si se tiene acceso de sólo lectura (r) o de lectura/escritura (r/w). Por ejemplo, se puede abrir un fichero de una base de datos para actualización:

RandomAccessFile miRAFile;

miRAFile = new RandomAccessFile( "/tmp/kk.dbf","rw" );

Acceso a la Información

Los objetos RandomAccessFile esperan información de lectura/escritura de la misma manera que los objetos DataInput/DataOutput. Se tiene acceso a todas las operaciones read() y write() de las clases DataInputStream y DataOutputStream.

También se tienen muchos métodos para moverse dentro de un fichero:

long getFilePointer();

Devuelve la posición actual del puntero del fichero

void seek( long pos );

Coloca el puntero del fichero en una posición determinada. La posición se da como un desplazamiento en bytes desde el comienzo del fichero. La posición 0 marca el comienzo de ese fichero.

179

Page 180: Programacion en Java

long length();

Devuelve la longitud del fichero. La posición length() marca el final de ese fichero.

Actualización de Información

Se pueden utilizar ficheros de acceso aleatorio para añadir información a ficheros existentes:

miRAFile = new RandomAccessFile( "/tmp/kk.log","rw" );

miRAFile.seek( miRAFile.length() );

// Cualquier write() que hagamos a partir de este punto del código

// añadirá información al fichero

Vamos a ver un pequeño ejemplo, Log.java, que añade una cadena a un fichero existente:

import java.io.*;

// Cada vez que ejecutemos este programita, se incorporara una nueva

// linea al fichero de log que se crea la primera vez que se ejecuta

//

//

class Log {

public static void main( String args[] ) throws IOException {

RandomAccessFile miRAFile;

String s = "Informacion a incorporar\nTutorial de Java\n";

// Abrimos el fichero de acceso aleatorio

miRAFile = new RandomAccessFile( "/tmp/java.log","rw" );

// Nos vamos al final del fichero

miRAFile.seek( miRAFile.length() );

// Incorporamos la cadena al fichero

miRAFile.writeBytes( s );

// Cerramos el fichero

miRAFile.close();

180

Page 181: Programacion en Java

}

}

13. COMUNICACIONES EN JAVA

13.1 Modelo de Comunicaciones con Java

En Java, crear una conexión socket TCP/IP se realiza directamente con el paquete java.net. A continuación mostramos un diagrama de lo que ocurre en el lado del cliente y del servidor:

El modelo de sockets más simple es:

El servidor establece un puerto y espera durante un cierto tiempo (timeout segundos), a que el cliente establezca la conexión. Cuando el cliente solicite una conexión, el servidor abrirá la conexión socket con el método accept().

El cliente establece una conexión con la máquina host a través del puerto que se designe en puerto# .

El cliente y el servidor se comunican con manejadores InputStream y OutputStream.

Hay una cuestión al respecto de los sockets, que viene impuesta por la implementación del sistema de seguridad de Java. Actualmente, los applets sólo pueden establecer conexiones con el nodo desde el cual se transfirió su código. Esto está implementado en el JDK y en el intérprete de Java de Netscape. Esto reduce en gran manera la flexibilidad de las fuentes de datos disponibles para los applets. El problema si se permite que un applet se conecte a cualquier máquina de la red, es que entonces se podrían utilizar los applets para inundar la red desde un ordenador con un cliente Netscape del que no se sospecha y sin ninguna posibilidad de rastreo.

13.2 Clases Utiles en Comunicaciones

Vamos a exponer otras clases que resultan útiles cuando estamos desarrollando programas de comunicaciones, aparte de las que ya se han visto. El problema es que la mayoría de estas clases se prestan a discusión, porque se encuentran bajo el directorio sun. Esto quiere decir que son

181

Page 182: Programacion en Java

implementaciones Solaris y, por tanto, específicas del Unix Solaris. Además su API no está garantizada, pudiendo cambiar. Pero, a pesar de todo, resultan muy interesantes y vamos a comentar un grupo de ellas solamente que se encuentran en el paquete sun.net.

Socket

Es el objeto básico en toda comunicación a través de Internet, bajo el protocolo TCP. Esta clase proporciona métodos para la entrada/salida a través de streams que hacen la lectura y escritura a través de sockets muy sencilla.

ServerSocket

Es un objeto utilizado en las aplicaciones servidor para escuchar las peticiones que realicen los clientes conectados a ese servidor. Este objeto no realiza el servicio, sino que crea un objeto Socket en función del cliente para realizar toda la comunicación a través de él.

DatagramSocket

La clase de sockets datagrama puede ser utilizada para implementar datagramas o fiables (sockets UDP), no ordenados. Aunque la comunicación por estos sockets es muy rápida porque no hay que perder tiempo estableciendo la conexión entre cliente y servidor.

DatagramPacket

Clase que representa un paquete datagrama conteniendo información de paquete, longitud de paquete, direcciones Internet y números de puerto.

MulticastSocket

Clase utilizada para crear una versión multicast de las clase socket datagrama. Múltiples clientes/servidores pueden transmitir a un grupo multicast (un grupo de direcciones IP compartiendo el mismo número de puerto).

NetworkServer

Una clase creada para implementar métodos y variables utilizadas en la creación de un servidor TCP/IP.

NetworkClient

Una clase creada para implementar métodos y variables utilizadas en la creación de un cliente TCP/IP.

SocketImpl

Es un Interface que nos permite crearnos nuestro propio modelo de comunicación. Tendremos que implementar sus métodos cuando la usemos. Si vamos a desarrollar

una aplicación con requerimientos especiales de comunicaciones, como pueden se la implementación de un cortafuegos (TCP es un protocolo no seguro), o acceder a equipos especiales (como un lector de código de barras o un GPS diferencial), necesitaremos nuestra propia clase Socket.

182

Page 183: Programacion en Java

14. ARQUITECTURA Modelo/Vista/Controlador

La arquitectura MVC (Model/View/Controller) fue introducida como parte de la versión Smalltalk-80 del lenguaje de programación Smalltalk. Fue diseñada para reducir el esfuerzo de programación necesario en la implementación de sistemas múltiples y sincronizados de los mismos datos. Sus características principales son que el Modelo, las Vistas y los Controladores se tratan como entidades separadas; esto hace que cualquier cambio producido en el Modelo se refleje automáticamente en cada una de las Vistas.

Además del programa ejemplo que hemos presentado al principio y que posteriormente implementaremos, este modelo de arquitectura se puede emplear en sistemas de representación gráfica de datos, como se ha citado, o en sistemas CAD, en donde se presentan partes del diseño con diferente escala de aumento, en ventanas separadas.

En la figura siguiente, vemos la arquitectura MVC en su forma más general. Hay un Modelo, múltiples Controladores que manipulan ese Modelo, y hay varias Vistas de los datos del Modelo, que cambian cuando cambia el estado de ese Modelo.

Este modelo de arquitectura presenta varias ventajas:

Hay una clara separación entre los componentes de un programa; lo cual nos permite implementarlos por separado.

Hay un API muy bien definido; cualquiera que use el API, podrá reemplazar el Modelo, la Vista o el Controlador, sin aparente dificultad.

La conexión entre el Modelo y sus Vistas es dinámica; se produce en tiempo de ejecución, no en tiempo de compilación.

Al incorporar el modelo de arquitectura MVC a un diseño, las piezas de un programa se pueden construir por separado y luego unirlas en tiempo de ejecución. Si uno de los Componentes, posteriormente, se observa que funciona mal, puede reemplazarse sin que las otras piezas se vean afectadas. Este escenario contrasta con la aproximación monolítica típica de muchos programas Java. Todos tienen un Frame que contiene todos los elementos, un controlador de eventos, un montón de cálculos y la presentación del resultado. Ante esta perspectiva, hacer un cambio aquí no es nada trivial.

14.1 Definición de las partes

El Modelo es el objeto que representa los datos del programa. Maneja los datos y controla todas sus transformaciones. El Modelo no tiene conocimiento específico de los Controladores o de las Vistas, ni siquiera contiene referencias a ellos. Es el propio sistema el que tiene encomendada la responsabilidad de mantener enlaces entre el Modelo y sus Vistas, y notificar a las Vistas cuando cambia el Modelo.

183

Page 184: Programacion en Java

La Vista es el objeto que maneja la presentación visual de los datos representados por el Modelo. Genera una representación visual del Modelo y muestra los datos al usuario. Interactúa con el Modelo a través de una referencia al propio Modelo.

El Controlador es el objeto que proporciona significado a las ordenes del usuario, actuando sobre los datos representados por el Modelo. Cuando se realiza algún cambio, entra en acción, bien sea por cambios en la información del Modelo o por alteraciones de la Vista. Interactúa con el Modelo a través de una referencia al propio Modelo.

Vamos a mostrar un ejemplo concreto. Consideremos como tal el sistema descrito en la introducción a este capítulo, una pieza geométrica en tres dimensiones, que representamos en la figura siguiente:

En este caso, la pieza central de la escena en tres dimensiones es el Modelo. El Modelo es una descripción matemática de los vértices y las caras que componen la escena. Los datos que describen cada vértice o cara pueden modificarse (quizás como resultado de una acción del usuario, o una distorsión de la escena, o un algoritmo de sombreado). Sin embargo, no tiene noción del punto de vista, método de presentación, perspectiva o fuente de luz. El Modelo es una representación pura de los elementos que componen la escena.

La porción del programa que transforma los datos dentro del Modelo en una presentación gráfica es la Vista. La Vista incorpora la visión del Modelo a la escena; es la representación gráfica de la escena desde un punto de vista determinado, bajo condiciones de iluminación determinadas.

El Controlador sabe que puede hacer el Modelo e implementa el interface de usuario que permite iniciar la acción. En este ejemplo, un panel de datos de entrada es lo único que se necesita, para permitir añadir, modificar o borrar vértices o caras de la figura.

184

Page 185: Programacion en Java

¿Qué es Visual Basic? La palabra "Visual" hace referencia al método que se utiliza para crear la interfaz gráfica de usuario (GUI). En lugar de escribir numerosas líneas de código para describir la apariencia y la ubicación de los elementos de la interfaz, simplemente puede arrastrar y colocar objetos prefabricados en su lugar dentro de la pantalla. Si ha utilizado alguna vez un programa de dibujo como Paint, ya tiene la mayor parte de las habilidades necesarias para crear una interfaz de usuario efectiva.

La palabra "Basic" hace referencia al lenguaje BASIC (Beginners All-Purpose Symbolic Instruction Code), un lenguaje utilizado por más programadores que ningún otro lenguaje en la historia de la informática o computación. Visual Basic ha evolucionado a partir del lenguaje BASIC original y ahora contiene centenares de instrucciones, funciones y palabras clave, muchas de las cuales están directamente relacionadas con la interfaz gráfica de Windows. Los principiantes pueden crear aplicaciones útiles con sólo aprender unas pocas palabras clave, pero, al mismo tiempo, la eficacia del lenguaje permite a los profesionales acometer cualquier objetivo que pueda alcanzarse mediante cualquier otro lenguaje de programación de Windows.

El lenguaje de programación Visual Basic no es exclusivo de Visual Basic. La Edición para aplicaciones del sistema de programación de Visual Basic, incluida en Microsoft Excel, Microsoft Access y muchas otras aplicaciones Windows, utilizan el mismo lenguaje. El sistema de programación de Visual Basic, Scripting Edition (VBScript) para programar en Internet es un subconjunto del lenguaje Visual Basic. La inversión realizada en el aprendizaje de Visual Basic le ayudará a abarcar estas otras áreas.

Si su objetivo es crear un pequeño programa para su uso personal o para su grupo de trabajo, un sistema para una empresa o incluso aplicaciones distribuidas de alcance mundial a través de Internet, Visual Basic dispone de las herramientas que necesita.

• Las características de acceso a datos le permiten crear bases de datos y aplicaciones cliente para los formatos de las bases de datos más conocidas, incluidos Microsoft SQL Server y otras bases de datos de ámbito empresarial.

• Las tecnologías ActiveX™ le permiten utilizar la funcionalidad proporcionada por otras aplicaciones, como el procesador de textos Microsoft Word, la hoja de cálculo Microsoft Excel y otras aplicaciones Windows. Puede incluso automatizar las aplicaciones y los objetos creados con la Edición profesional o la Edición empresarial de Visual Basic.

• Las capacidades de Internet facilitan el acceso a documentos y aplicaciones a través de Internet desde su propia aplicación.

• La aplicación terminada es un auténtico archivo .exe que utiliza una biblioteca de vínculos dinámicos (DLL) de tiempo de ejecución que puede distribuir con toda libertad.

Ediciones de Visual Basic

Visual Basic se encuentra disponible en tres versiones, cada una de las cuales está orientada a unos requisitos de programación específicos.

Curso de Visual Basic I

185

Page 186: Programacion en Java

• La Edición de aprendizaje de Visual Basic permite a los programadores crear robustas aplicaciones para Microsoft Windows 95 y Windows NT®. Incluye todos los controles intrínsecos, además de los controles de cuadrícula, de fichas y los controles enlazados a datos. La documentación que se proporciona con esta edición incluye Learn VB Now (un CD-ROM multimedia), un Manual del programador impreso, la Ayuda en pantalla y los Libros en pantalla de Visual Basic.

• La Edición profesional proporciona a los profesionales un completo conjunto de herramientas para desarrollar soluciones para terceros. Incluye todas las características de la Edición de aprendizaje, así como controles ActiveX adicionales, incluidos controles para Internet y el Generador de informes de Crystal Reports. La documentación que se proporciona con la Edición profesional incluye el Manual del programador, la Ayuda en pantalla, la Guía de herramientas componentes y el Manual del usuario de Crystal Reports para Visual Basic.

• La Edición empresarial permite a los profesionales crear sólidas aplicaciones distribuidas en un entorno de equipo. Incluye todas las características de la Edición profesional, asó como el Administrador de automatización, la Galería de objetos, las herramientas de administración de bases de datos, el sistema de control de versiones orientado a proyectos Microsoft Visual SourceSafe™, etc. La documentación impresa que se proporciona con la Edición empresarial incluye toda la documentación de la Edición profesional, y la Guía para la creación de aplicaciones cliente-servidor con Visual Basic y el Manual del usuario de SourceSafe.

1- Introducción:

1.1- Programación orientada a objetos.

1.1.1- Objetos.

Un objeto es una entidad que tiene asociado un conjunto de métodos, eventos y propiedades. Ejemplo: Una caja de texto (TextBox) en la cual podemos escribir cualquier línea es un objeto.

1.1.2- Propiedades.

Son las características que posee un objeto o un formulario (ventana de Windows).

Ejemplo: Color de fondo del formulario, Fuente de texto de un TextBox, ….

1.1.3- Métodos.

Los métodos son funciones internas de un determinado objeto que permite realizar funciones sobre él o sobre otro objeto.

Ejemplo: Deseamos poner en la ventana Windows de nuestra aplicación "Hola mundo", por tanto pondremos el método -> Ventana.Print "Hola mundo"

1.1.4- Eventos.

186

Page 187: Programacion en Java

Los eventos son acciones que se pueden realizar en cualquier control: click, doble click, movimiento del ratón. A estos eventos se les puede asociar código para que se ejecute al producir el evento.

Un programa Visual Basic es un POE (Programa orientado a eventos).

Todo lo que hacemos en un programa Visual Basic está generado por medio de eventos

1.1.5- Explicación integrada y ejemplo de Objetos, Propiedades, Métodos y Eventos.

Los formularios y controles de Visual Basic son objetos que exponen sus propios métodos, propiedades y eventos. Las propiedades se pueden considerar como atributos de un objeto, los métodos como sus acciones y los eventos como sus respuestas.

Un objeto de uso diario como el globo de un niño tiene también propiedades, métodos y eventos. Entre las propiedades de un globo se incluyen atributos visibles como el peso, el diámetro y el color. Otras propiedades describen su estado (inflado o desinflado) o atributos que no son visibles, como su edad. Por definición, todos los globos tienen estas propiedades; lo que varía de un globo a otros son los valores de estas propiedades.

Un globo tiene también métodos o acciones inherentes que puede efectuar. Tiene un método inflar (la acción de llenarlo de helio) o un método desinflar (expeler su contenido) y un método elevarse (si se deja escapar). De nuevo, todos los globos pueden efectuar estos métodos.

Los globos tienen además respuestas predefinidas a ciertos eventos externos. Por ejemplo, un globo respondería al evento de pincharlo desinflándose o al evento de soltarlo elevándose en el aire.

Los objetos tienen propiedades, responden a eventos y ejecutan métodos:

Si se pudiera programar un globo, el código de Visual Basic podría ser como el siguiente. Para establecer las propiedades del globo:

Globo.Color = Rojo

Globo.Diametro = 10

Globo.Inflado = True

Observe la sintaxis del código: el objeto (Globo) seguido de la propiedad (Color) seguida de la asignación del valor (Rojo). Podría modificar el color del globo desde el código si repitiera esta instrucción y sustituyera el valor por otro diferente. Las propiedades también se pueden establecer en la ventana Propiedades mientras se está diseñando la aplicación.

Los métodos de un globo se invocan de esta forma:

Globo.Inflar

Globo.Desinflar

Globo.Elevar 5

187

Page 188: Programacion en Java

La sintaxis es similar a la sintaxis de las propiedades: el objeto (un nombre) seguido de un método (un verbo). En el tercer ejemplo hay un elemento adicional, llamado argumento, que indica la distancia que se eleva. Algunos métodos tendrán uno o más argumentos para describir más a fondo la acción que se va a ejecutar.

El globo puede responder a un evento como se muestra a continuación:

Sub Globo_Pinchazo()

Globo.Desinflar

Globo.HacerRuido "Bang"

Globo.Inflado = False

Globo.Diámetro = 1

End Sub

En este caso, el código describe el comportamiento del globo cuando se produce un evento Pinchazo: invoca el método Desinflar y luego invoca el método HacerRuido con un argumento “Bang” (el tipo de ruido que se va a hacer). Como el globo ya no está inflado, la propiedad Inflado tiene el valor False y la propiedad Diámetro adopta un nuevo valor.

Si bien no puede programar un globo, sí puede programar un formulario o un control de Visual Basic. Como programador, tiene el control: decide qué propiedades se deben modificar, qué métodos se deben invocar o a qué eventos hay que responder para conseguir la apariencia y el comportamiento deseados

1.1.6- Diferencias entre la programación procedural y la programación bajo Windows.

Un estudio profundo del funcionamiento interno de Windows necesitaría un libro completo. No es necesario tener un profundo conocimiento de todos los detalles técnicos. Una versión reducida del funcionamiento de Windows incluye tres conceptos clave: ventanas, eventos y mensajes.

Una ventana es simplemente una región rectangular con sus propios límites. Probablemente ya sabe que hay varios tipos de ventanas: una ventana Explorador en Windows 95, una ventana de documento dentro de su programa de proceso de textos o un cuadro de diálogo que emerge para recordarle una cita. Aunque éstos son los ejemplos más comunes, realmente hay otros muchos tipos de ventanas. Un botón de comando es una ventana. Los iconos, cuadros de texto, botones de opción y barras de menús son todos ventanas.

El sistema operativo Microsoft Windows administra todas estas ventanas asignando a cada una un único número identificador (controlador de ventana o hWnd). El sistema controla continuamente cada una de estas ventanas para ver si existen signos de actividad o eventos. Los eventos pueden producirse mediante acciones del usuario, como hacer clic con el mouse (ratón) o presionar una tecla, mediante programación o incluso como resultado de acciones de otras ventanas.

Cada vez que se produce un evento se envía un mensaje al sistema operativo. El sistema procesa el mensaje y lo transmite a las demás ventanas. Entonces, cada ventana puede realizar la acción apropiada, basándose en sus propias instrucciones para tratar ese mensaje en particular (por ejemplo, volverse a dibujar cuando otra ventana la ha dejado al descubierto).

188

Page 189: Programacion en Java

Como puede imaginar, tratar todas las combinaciones posibles de ventanas, eventos y mensajes podría ser interminable. Afortunadamente, Visual Basic le evita tener que tratar con todos los controladores de mensajes de bajo nivel. Muchos de los mensajes los controla automáticamente Visual Basic, mientras que otros se tratan como procedimientos de evento para su comodidad. Esto le permite crear rápidamente eficaces aplicaciones sin tener que tratar detalles innecesarios.

En las aplicaciones tradicionales o "por procedimientos", la aplicación es la que controla qué partes de código y en qué secuencia se ejecutan. La ejecución comienza con la primera línea de código y continúa con una ruta predefinida a través de la aplicación, llamando a los procedimientos según se necesiten.

En una aplicación controlada por eventos, el código no sigue una ruta predeterminada; ejecuta distintas secciones de código como respuesta a los eventos. Los eventos pueden desencadenarse por acciones del usuario, por mensajes del sistema o de otras aplicaciones, o incluso por la propia aplicación. La secuencia de estos eventos determina la secuencia en la que se ejecuta el código, por lo que la ruta a través del código de la aplicación es diferente cada vez que se ejecuta el programa.

Puesto que no puede predecir la secuencia de los eventos, el código debe establecer ciertos supuestos acerca del "estado del mundo" cuando se ejecute. Cuando haga suposiciones (por ejemplo, que un campo de entrada debe contener un valor antes de ejecutar un procedimiento para procesar ese valor), debe estructurar la aplicación de forma que asegure que esa suposición siempre será válida (por ejemplo, deshabilitando el botón de comando que inicia el procedimiento hasta que el campo de entrada contenga un valor).

El código también puede desencadenar eventos durante la ejecución. Por ejemplo, cambiar mediante programación el texto de un cuadro de texto hace que se produzca el evento Change del cuadro de texto. Esto causaría la ejecución del código (si lo hay) contenido en el evento Change. Si supone que este evento sólo se desencadenará mediante la interacción del usuario, podría ver resultados inesperados. Por esta razón es importante comprender el modelo controlado por eventos y tenerlo en cuenta cuando diseñe su aplicación.

1.3- Proyecto.

1.3.1- Definición de Proyecto en Visual Basic.

Para crear una aplicación con Visual Basic se trabaja con proyectos. Un proyecto es una colección de archivos que se usan para generar una aplicación. Este tema describe cómo generar y administrar proyectos.

Al crear una aplicación probablemente creará nuevos formularios; también puede volver a usar o modificar formularios creados en proyectos anteriores. Esto también se aplica a otros módulos o archivos que pueda incluir en su proyecto. Los controles ActiveX y los objetos de otras aplicaciones también se pueden compartir entre proyectos.

Después de ensamblar todos los componentes de un proyecto y escribir el código, puede compilar el proyecto para crear un archivo ejecutable.

1.3.2-Componentes de un Proyecto.

Cuando desarrolla un aplicación, trabaja con un archivo de proyecto para administrar todos los diferentes archivos que crea. Un proyecto consta de lo siguiente:

189

Page 190: Programacion en Java

• Un archivo de proyecto que realiza el seguimiento de todos los componentes (.vbp) • Un archivo para cada formulario (.frm). • Un archivo de datos binario para cada formulario que contenga datos sobre

propiedades de controles del formulario (.frx). Estos archivos no se pueden modificar y los genera automáticamente cualquier archivo .frm que tenga propiedades en formato binario, como Picture o Icon.

• Opcionalmente, un archivo para cada módulo de clase (.cls). • Opcionalmente, un archivo para cada módulo estándar (.bas). • Opcionalmente, uno o más archivos con controles ActiveX (.ocx). • Opcionalmente, un único archivo de recursos (.res).

El archivo de proyecto es simplemente una lista de todos los archivos y objetos asociados con el proyecto, así como información sobre las opciones de entorno establecidas. Esta información se actualiza cada vez que guarda el proyecto. Todos los archivos y objetos también se pueden compartir con otros proyectos.

Cuando ha completado todos los archivos del proyecto puede convertir el proyecto en un archivo ejecutable (.exe): en el menú Archivo, elija el comando Generar proyecto.exe.

1.3.2.1-Formularios.

Un formulario es una ventana. La ventana Windows de cualquier aplicación.

Podemos abrir tantas ventanas como queramos en nuestro proyecto, pero el nombre de las ventanas debe ser distinto. Por defecto como ya hemos visto, la ventana que se abre en Visual Basic tiene el nombre de Form1. Ya veremos como cambiar estas "Propiedades" más adelante.

Los módulos de formularios (extensión de nombre de archivo .frm) pueden contener descripciones en forma de texto del formulario y sus controles, incluyendo los valores de sus propiedades. También pueden contener declaraciones a nivel de formulario de constantes, variables y procedimientos externos, procedimientos de evento y procedimientos generales.

1.3.2.2-Módulos de clase.

Los módulos de clase (extensión de nombre de archivo .cls) son similares a los módulos de formulario, excepto en que no tiene interfaz de usuario visible. Puede usar módulos de clase para crear sus propios objetos, incluyendo código para métodos y propiedades.

1.3.2.3-Módulos estándar.

Un módulo es un archivo Visual Basic donde escribimos parte del código de nuestro programa, y digo parte, porque puede haber código en el formulario también.

Las rutinas incluidas dentro de los módulos pueden ser ejecutadas desde los formularios de la aplicación.

Los módulos estándar (extensión de nombre de archivo .bas) pueden contener declaraciones públicas o a nivel de módulo de tipos, constantes, variables, procedimientos externos y procedimientos públicos.

1.3.2.4-Archivos de Recursos.

190

Page 191: Programacion en Java

Los archivos de recursos (extensión de nombre de archivo .res) contienen mapas de bits, cadenas de texto y otros datos que puede modificar sin volver a modificar el código. Por ejemplo, si piensa traducir su aplicación a un idioma extranjero, puede guardar todas las cadenas de texto de la interfaz de usuario y los mapas de bits en un archivo de recursos, y simplemente traducir el archivo de recursos en vez de la aplicación completa. Un proyecto sólo puede contener un archivo de recursos.

1.3.2.5-Controles Active X.

Los controles ActiveX (extensión de nombre de archivo .ocx) son controles opcionales que se pueden agregar al cuadro de herramientas y se pueden usar en formularios. Cuando instala Visual Basic, los archivos que contienen los controles incluidos en Visual Basic se copian a un directorio común (el subdirectorio \Windows\System en Windows 95). Existen controles ActiveX adicionales disponibles en diversas fuentes. También puede crear sus propios controles mediante las ediciones Profesional y Empresarial de Visual Basic.

1.3.2.6-Controles estándar.

Los controles estándar los proporciona Visual Basic. Los controles estándar, como CommandButton (botón de comando) o Frame (marco), siempre están incluidos en el cuadro de herramientas, al contrario de lo que ocurre con los controles ActiveX y los objetos insertables, que se pueden agregar y quitar del cuadro de herramientas.

1.4-Entorno de Desarrollo.

1.4.1-Barra de menú. y 1.4.2-Barra de Herramientas.

En la ventana del programa, podemos hacer todas las funciones normales que nos permite el compilador Visual Basic.

1.4.3-Ventana de Proyecto.

Pulse "Ctrl+R" (Ver -> Proyecto) y se abrirá la ventana de proyectos (3).

En esta ventana tenemos todos los ficheros del proyecto Visual Basic en el que vamos a trabajar.

1.4.4- Formulario.

191

Page 192: Programacion en Java

Al principio y por defecto, el programa abre un formulario con el nombre Form1 que es la ventana Windows de nuestra aplicación.

1.4.5- Ventana de Propiedades.

Pulsando la tecla "F4", aparecerá la ventana de propiedades . Esta ventana es fundamental, ya que contiene todas las propiedades de cada objeto que insertaremos en nuestro formulario, así como las propiedades del formulario en sí.

1.4.6- Caja de Herramientas.

192

Page 193: Programacion en Java

La ventana caja de herramientas contiene todos los objetos que podemos incluir en nuestro formulario. Esta ventana se puede abrir en el menú principal (Ver -> Caja de herramientas).

2-Programación:

2.1- Fundamentos de la Programación.

Cada módulo de formulario contiene procedimientos de evento (secciones de código donde se colocan las instrucciones que se ejecutarán como respuesta a eventos específicos). Los formularios pueden contener controles. Por cada control de un formulario, existe el correspondiente conjunto de procedimientos de evento en el módulo de formulario. Además de procedimientos de evento, los módulos de formulario pueden contener procedimientos generales que se ejecutan como respuesta a una llamada desde cualquier procedimiento de evento.

El código que no esté relacionado con un control o un formulario específico se puede colocar en un tipo diferente de módulo, un módulo estándar (.bas). Se deben colocar en un módulo estándar los procedimientos que se puedan utilizar como respuesta a eventos de diversos objetos, en lugar de duplicar el código en los procedimientos de evento de cada objeto.

2.1.1- Como funciona una aplicación controlada por eventos.

Un evento es una acción reconocida por un formulario o un control. Las aplicaciones controladas por eventos ejecutan código Basic como respuesta a un evento. Cada formulario y control de Visual Basic tiene un conjunto de eventos predefinidos. Si se produce uno de dichos eventos y el procedimiento de evento asociado tiene código, Visual Basic llama a ese código.

Aunque los objetos de Visual Basic reconocen automáticamente un conjunto predefinido de eventos, usted decide cuándo y cómo se responderá a un evento determinado. A cada evento le corresponde una sección de código (un procedimiento de evento). Cuando desea que un control responda a un evento, escribe código en el procedimiento de ese evento.

Los tipos de eventos reconocidos por un objeto varían, pero muchos tipos son comunes a la mayoría de los controles. Por ejemplo, la mayoría de los objetos reconocen el evento Click: si un usuario hace clic en un formulario, se ejecuta el código del procedimiento de evento Click del formulario; si un usuario hace clic en un botón de comando, se ejecuta el código del procedimiento de evento Click del botón. El código en cada caso será diferente.

193

Page 194: Programacion en Java

He aquí una secuencia típica de eventos en una aplicación controlada por eventos:

1. Se inicia la aplicación y se carga y muestra un formulario.

2. El formulario (o un control del formulario) recibe un evento. El evento puede estar causado por el usuario (por ejemplo, por la pulsación de una tecla), por el sistema (por ejemplo, un evento de cronómetro) o, de forma indirecta, por el código (por ejemplo, un evento Load cuando el código carga un formulario).

3. Si hay código en el procedimiento de evento correspondiente, se ejecuta.

4. La aplicación espera al evento siguiente.

2.2- Variables.

2.2.1- Alcance de las variables.

El alcance de una variable define qué partes del código son conscientes de su existencia. Cuando declara una variable en un procedimiento, sólo el código de dicho procedimiento puede tener acceso o modificar el valor de la variable; tiene un alcance que es local al procedimiento. A veces, sin embargo, se necesita utilizar una variable con un alcance más general, como aquella cuyo valor está disponible para todos los procedimientos del mismo módulo o incluso para todos los procedimientos de toda la aplicación. Visual Basic le permite especificar el alcance de una variable cuando la declara.

Establecimiento del alcance de las variables

Dependiendo de cómo se declara, una variable tiene como alcance un procedimiento (local) o un módulo.

Alcance Privado Público

Nivel de procedimiento Las variables son privadas del procedimiento en el que aparecen.

No es aplicable. No puede declarar variables públicas dentro de un procedimiento.

Nivel de módulo Las variables son privadas del módulo en el que aparecen.

Las variables están disponibles para todos los módulos.

Variables utilizadas en un procedimiento

Las variables a nivel de procedimiento sólo se reconocen en el procedimiento en el que se han declarado. Se las conoce también como variables locales. Se declaran mediante las palabras clave Dim o Static. Por ejemplo:

Dim intTemp As Integer

o bien

Static intPermanent As Integer

Los valores de variables locales declaradas con Static existen mientras se ejecuta la aplicación, mientras que las variables declaradas con Dim sólo existen mientras se ejecuta el procedimiento.

194

Page 195: Programacion en Java

Las variables locales resultan una elección apropiada para cálculos temporales. Por ejemplo, puede crear una docena de procedimientos distintos que contengan una variable llamada intTemp. Como cada intTemp se ha declarado como una variable local, cada procedimiento sólo reconoce su propia versión de intTemp. Cualquier procedimiento puede alterar el valor de su intTemp local sin que ello afecte a las variables intTemp de los demás procedimientos.

Variables utilizadas en un módulo

De forma predeterminada, una variable a nivel de módulo está disponible para todos los procedimientos del módulo, pero no para el código de otros módulos. Cree variables a nivel de módulo declarándolas con la palabra clave Private en la sección Declaraciones al principio del módulo. Por ejemplo:

Private intTemp As Integer

A nivel de módulo, no hay diferencia entre Private y Dim, pero es preferible Private porque contrasta con Public y hace que el código sea más fácil de comprender.

Variables utilizadas por todos los módulos

Para hacer que una variable a nivel de módulo esté disponible para otros módulos, utilice la palabra clave Public para declarar la variable. Los valores de las variables públicas están disponibles para todos los procedimientos de la aplicación. Al igual que todas las variables a nivel de módulo, las variables públicas se declaran en la sección Declaraciones al principio del módulo. Por ejemplo:

Public intTemp As Integer

Nota: No puede declarar variables públicas en un procedimiento, sólo en la sección Declaraciones de un módulo.

2.2.2- Declaración

La forma de declarar las variables es la siguiente:

Dim Public Static nombre_variable As tipo

Dim: Al declarar una variable con esta palabra estamos diciendo que la variable sea local al ámbito en que se declara. Puede ser dentro de un procedimiento o dentro de un formulario, de esta forma no sería accesible desde los demás procedimientos o formularios.

Public: Las variables declaradas serán publicas y podrán estar accesibles desde todos los formularios de la aplicación. Para conseguirlo tendremos que declararlas en un módulo de código, no en la sección declarations de cualquier formulario de los que conste la aplicación. Para crear un módulo de código en el menú principal de Visual Basic marcamos en PROYECTO/INSETAR MÓDULO y aparecerá junto a los demás formularios de la ventana de proyecto aunque con un icono distinto indicando que se trata de un módulo de código.

Static: Con esta forma de declarar variables conseguiremos que las variables locales no se creen y se destruyan al entrar y salir de los procedimientos donde fueron declaradas sino que se mantenga su valor durante todo el periodo de ejecución de la aplicación. De esta forma a entrar en algún procedimiento las variables recuerdan el valor que tenían cuando se salió de él.

195

Page 196: Programacion en Java

2.2.3- Tipos de variables

PRIVATE<TBODY>TIPO COMENTARIOBOOLEAN Sólo admite 2 valores TRUE o FALSEBYTE admite valores entre 0 y 255INTEGER admite valores entre -32768 y 32767

LONGadmite valores entre -2.147.483.648 y 2.147.483.647

SINGLE admite valores decimales con precisión simpleDOUBLE admite valores decimales de doble precisiónCURRENCY válido para valores de tipo monedaSTRING cadenas de caracteresDATE fechas, permite operar con ellas</TBODY>

2.2.4- Matrices

Para declarar matrices debemos colocar entre paréntesis el número de elementos de los que constará a continuación del nombre de la variable:

Dim medidas(9) as integer

De esta forma tenemos una matriz de 10 elementos identificados del 0 al 9

Podemos obligar a que el primer elemento de una matriz tenga el índice con valor 1.

Esto lo haremos colocando la instrucción option base 1 en la sección declarations de nuestro formulario.

También podemos indicar los límites inferior y superior de la matriz:

Dim medidas(5 to 14) as integer

es una matriz de 10 elementos cuyos indices van del 5 al 14

Las matrices multidimensionales se declaran de la siguiente forma:

Dim medidas(1 to 10, 1 to 10) as integer

CONSIDERACIONES

Al trabajar con Visual Basic es preferible que activemos la opción que nos obligue a declarar todas las variables que utilicemos, de esta forma nos ahorraremos errores inesperados como el de trabajar con una variable ya utilizada anteriormente produciéndose un conflicto difícil de resolver. En cambio si intentamos declarar 2 variables con el mismo nombre, en el mismo formulario o procedimiento se produce un error en tiempo de edición avisándonos de la situación.

Para activar esta opción debemos ir a la opción del menú Herramientas y Opciones para que aparezca un cuadro de dialogo como este.

196

Page 197: Programacion en Java

La opción que nos interesa activar es Requerir declaración de variables que en este caso ya lo está. De esta forma en cada sección declarations de cada formulario aparecerá la sentencia option explicit

Otra opción que es interesante activar, es la de Guardar los cambios en la ficha entorno, la cual te guarda una copia del código antes de ejecutarlo por si acaso luego no podemos, se bloquea la aplicación etc... no suele pasar pero nunca se sabe. De esta forma te aseguras que lo último que hayas ejecutado lo tienes guardado en el disco.

La opción Comprobación automática de sintaxis normalmente viene activada por defecto, no conviene desactivarla puesto que te avisa de errores de sintaxis conforme vas escribiendo el código: Si te falta el then después del if, el do antes del while etc...

2.3- Procedimientos y funciones.

2.3.1-Introducción a los Procedimientos.

Puede simplificar las tareas de programación si divide los programas en componentes lógicos más pequeños. Estos componentes, llamados procedimientos, pueden convertirse en bloques básicos que le permiten mejorar y ampliar Visual Basic.

Los procedimientos resultan muy útiles para condensar las tareas repetitivas o compartidas, como cálculos utilizados frecuentemente, manipulación de texto y controles, y operaciones con bases de datos.

Hay dos ventajas principales cuando se programa con procedimientos:

• Los procedimientos le permiten dividir los programas en unidades lógicas discretas, cada una de las cuales se puede depurar más fácilmente que un programa entero sin procedimientos.

197

Page 198: Programacion en Java

• Los procedimientos que se utilizan en un programa pueden actuar como bloques de construcción de otros programas, normalmente con pocas o ninguna modificación.

En Visual Basic se utilizan varios tipos de procedimientos:

• Procedimientos Sub que no devuelven un valor.• Procedimientos Function que devuelven un valor (normalmente conocidos como

funciones).

2.3.2- Procedimientos.

Un procedimiento Sub es un bloque de código que se ejecuta como respuesta a un evento. Al dividir el código de un módulo en procedimientos Sub, es más sencillo encontrar o modificar el código de la aplicación.

La sintaxis de un procedimiento Sub es la siguiente:

[Private|Public][Static]Sub nombre_procedimiento (argumentos)

instrucciones

End Sub

Cada vez que se llama al procedimiento se ejecutan las instrucciones que hay entre Sub y End Sub. Se pueden colocar los procedimientos Sub en módulos estándar, módulos de clase y módulos de formulario. De forma predeterminada, los procedimientos Sub son Public en todos los módulos, lo que significa que se les puede llamar desde cualquier parte de la aplicación.

Los argumentos de un procedimiento son como las declaraciones de variables; se declaran valores que se pasan desde el procedimiento que hace la llamada.

Resulta muy útil en Visual Basic distinguir entre dos tipos de procedimientos Sub, procedimientos generales y procedimientos de evento.

Procedimientos generales

Un procedimiento general indica a la aplicación cómo realizar una tarea específica. Una vez que se define un procedimiento general, se le debe llamar específicamente desde la aplicación. Por el contrario, un procedimiento de evento permanece inactivo hasta que se le llama para responder a eventos provocados por el usuario o desencadenados por el sistema.

¿Por qué crear procedimientos generales? Una razón es que muchos procedimientos de evento distintos pueden necesitar que se lleven a cabo las mismas acciones. Es una buena estrategia de programación colocar las instrucciones comunes en un procedimiento distinto (un procedimiento general) y hacer que los procedimientos de evento lo llamen. Esto elimina la necesidad de duplicar código y también hace que la aplicación sea más fácil de mantener.

Procedimientos de evento

Cuando un objeto en Visual Basic reconoce que se ha producido un evento, llama automáticamente al procedimiento de evento utilizando el nombre correspondiente al evento. Como

198

Page 199: Programacion en Java

el nombre establece una asociación entre el objeto y el código, se dice que los procedimientos de evento están adjuntos a formularios y controles.

• Un procedimiento de evento de un control combina el nombre real del control (especificado en la propiedad Name), un carácter de subrayado (_) y el nombre del evento. Por ejemplo, si desea que un botón de comando llamado cmdPlay llame a un procedimiento de evento cuando se haga clic en él, utilice el procedimiento cmdPlay_Click.

• Un procedimiento de evento de un formulario combina la palabra "Form", un carácter de subrayado y el nombre del evento. Si desea que un formulario llame a un procedimiento de evento cuando se hace clic en él, utilice el procedimiento Form_Click. (Como los controles, los formularios tienen nombres únicos, pero no se utilizan en los nombres de los procedimientos de evento.)

Todos los procedimientos de evento utilizan la misma sintaxis general.

Sintaxis de un evento de control Sintaxis de un evento de formulario

Private Sub nombrecontrol_nombreevento (argumentos )

instrucciones

End Sub

Private Sub Form_nombreevento (argumentos)

instrucciones

End Sub

Aunque puede escribir procedimientos de evento nuevos, es más sencillo utilizar los procedimientos de código que facilita Visual Basic, que incluyen automáticamente los nombres correctos de procedimiento. Puede seleccionar una plantilla en la ventana Editor de código si selecciona un objeto en el cuadro Objeto y selecciona un procedimiento en el cuadro Procedimiento.

También es conveniente establecer la propiedad Name de los controles antes de empezar a escribir los procedimientos de evento para los mismos. Si cambia el nombre de un control tras vincularle un procedimiento, deberá cambiar también el nombre del procedimiento para que coincida con el nuevo nombre del control. De lo contrario, Visual Basic no será capaz de hacer coincidir el control con el procedimiento. Cuando el nombre de un procedimiento no coincide con el nombre de un control, se convierte en un procedimiento general.

2.3.3- Funciones.

La sintaxis de un procedimiento Function es la siguiente:

[Private|Public][Static]Function nombre_procedimiento (argumentos) [As tipo]

instrucciones

End Function

199

Page 200: Programacion en Java

Al igual que un procedimiento Sub, un procedimiento Function es un procedimiento diferente que puede tomar argumentos, realizar una serie de instrucciones y cambiar el valor de los argumentos. A diferencia de los procedimientos Sub, los procedimientos Function pueden devolver un valor al procedimiento que realiza la llamada. Hay tres diferencias entre los procedimientos Sub y Function:

• Generalmente, se llama a una función incluyendo el nombre y los argumentos del procedimiento en la parte derecha de una instrucción o expresión mayor (valor_retorno = función()).

• Los procedimientos Function tienen tipos de datos, al igual que las variables. Esto determina el tipo del valor de retorno. (En ausencia de la cláusula As, el tipo es el tipo predeterminado Variant.)

• Se devuelve un valor asignándole al propio nombre_procedimiento. Cuando el procedimiento Function devuelve un valor, se puede convertir en parte de una expresión mayor.

Por ejemplo, podría escribir una función que calculara el tercer lado, o hipotenusa, de un triángulo rectángulo, dados los valores de los otros dos lados:

Function Hipotenusa (A As Integer, B As Integer) As String

Hipotenusa = Sqr(A ^ 2 + B ^ 2)

End Function

Se llama a un procedimiento Function de la misma forma que a las funciones incorporadas en Visual Basic:

Label1.Caption = Hipotenusa(CInt(Text1.Text),CInt(Text2.Text))

strX = Hipotenusa(Width, Height)

2.3.4- Llamadas a procedimientos.

Un procedimiento Sub difiere de un procedimiento Function en que al procedimiento Sub no se le puede llamar mediante su nombre en una expresión. La llamada a un procedimiento Sub es una instrucción única. Además, un procedimiento Sub no devuelve un valor en su nombre como hace una función. Sin embargo, al igual que Function, un procedimiento Sub puede modificar los valores de las variables que se le pasan.

Hay dos formas de llamar a un procedimiento Sub:

Ambas instrucciones llaman a un Sub denominado MiProc.

Call MiProc (PrimerArgumento, SegundoArgumento)

MiProc PrimerArgumento, SegundoArgumento

Observe que cuando utiliza la sintaxis Call, debe poner los argumentos entre paréntesis. Si omite la palabra clave Call, deberá también omitir los paréntesis alrededor de los argumentos.

2.3.5- Llamadas a Funciones.

200

Page 201: Programacion en Java

Normalmente se llama a un procedimiento de función que se ha escrito de la misma forma en que se llama a una función intrínseca de Visual Basic como Abs; es decir, utilizando su nombre en una expresión:

Las instrucciones siguientes llamarían a una función

' llamada ToDec.

Print 10 * Adec

X = Adec

If Adec = 10 Then Debug.Print "Fuera del intervalo"

X = OtraFunción(10 * Adec)

También es posible llamar a una función igual que se llama a un procedimiento Sub. Las instrucciones siguientes llaman a la misma función:

Call Year(Now)

Year Now

Cuando llama a una función de esta manera, Visual Basic desecha el valor de retorno.

2.3.6- Pasaje de argumentos a los procedimientos y funciones.

Normalmente el código de un procedimiento necesita cierta información sobre el estado del programa para realizar su trabajo. Esta información consiste en variables que se pasan al procedimiento cuando se le llama. Cuando se pasa una variable a un procedimiento, se llama argumento.

Tipos de datos de los argumentos

Los argumentos de los procedimientos que escriba tienen el tipo de dato Variant de forma predeterminada. Sin embargo, puede declarar otros tipos de datos para los argumentos. Por ejemplo, la función siguiente acepta una cadena y un entero:

Function QuéComer (DíaSemana As String, Hora As Integer) As String

' Devuelve el menú del almuerzo basándose en el día y la hora.

If DíaSemana = "Viernes" then

QuéComer = "Pescado"

Else

QuéComer = "Pollo"

End If

201

Page 202: Programacion en Java

If Hora > 4 Then QuéComer = "Demasiado tarde"

End Function

Paso de argumentos por valor

Sólo se pasa una copia de la variable cuando se pasa un argumento por valor. Si el procedimiento cambia el valor, el cambio afecta sólo a la copia y no a la variable propiamente dicha. Utilice la palabra clave ByVal para indicar un argumento pasado por valor.

Por ejemplo:

Sub Cuentas (ByVal intNumCuenta as Integer)

.

. ' Ponga aquí sus instrucciones.

.

End Sub

Paso de argumentos por referencia

Pasar argumentos por referencia le da al procedimiento acceso al contenido real de la variable en su ubicación de dirección de memoria. Como resultado, el procedimiento al que se ha pasado el valor de la variable se puede modificar de forma permanente. La forma predeterminada de pasar valores en Visual Basic es por referencia.

Si especifica el tipo de dato de un argumento que se pasa por referencia, debe pasar un valor de ese tipo para el argumento. Puede eludirlo si pasa una expresión en vez de un tipo de dato como argumento. Visual Basic evalúa la expresión y la pasa como el tipo requerido si puede.

La forma más sencilla de convertir una variable en una expresión es ponerla entre paréntesis. Por ejemplo, para pasar una variable declarada como entero a un procedimiento que espera una cadena como argumento, debería hacer lo siguiente:

Sub ProcedimientoQueLlama ()

Dim intX As Integer

intX = 12 * 3

Foo(intX)

End Sub

Sub Foo(Bar As String)

MsgBox Bar 'El valor de Bar es la cadena "36".

End Sub

202

Page 203: Programacion en Java

2.4- Estructuras de Control (Repetición y Decisión).

2.4.1-Do While – Loop / Do – Loop While.

Utilice el bucle Do para ejecutar un bloque de instrucciones un número indefinido de veces. Hay algunas variantes en la instrucción Do...Loop, pero cada una evalúa una condición numérica para determinar si continúa la ejecución. Como ocurre con If...Then, la condición debe ser un valor o una expresión que dé como resultado False (cero) o True (distinto de cero).

En el ejemplo de Do...Loop siguiente, las instrucciones se ejecutan siempre y cuando condición sea True:

Do While condición

instrucciones

Loop

Cuando Visual Basic ejecuta este bucle Do, primero evalúa condición. Si condición es False (cero), se salta todas las instrucciones. Si es True (distinto de cero), Visual Basic ejecuta las instrucciones, vuelve a la instrucción Do While y prueba la condición de nuevo.

Por tanto, el bucle se puede ejecutar cualquier número de veces, siempre y cuando condición sea distinta de cero o True. Nunca se ejecutan las instrucciones si condición es False inicialmente. Por ejemplo, este procedimiento cuenta las veces que se repite una cadena de destino dentro de otra cadena repitiendo el bucle tantas veces como se encuentre la cadena de destino:

Function ContarCadenas (cadenalarga, destino)

Dim posición, contador

posición = 1

Do While InStr(posición, cadenalarga, destino)

posición = InStr(posición, cadenalarga, destino) +1

contador = contador + 1

Loop

ContarCadenas = contador

End Function

Si la cadena de destino no está en la otra cadena, InStr devuelve 0 y no se ejecuta el bucle.

Otra variante de la instrucción Do...Loop ejecuta las instrucciones primero y prueba condición después de cada ejecución. Esta variación garantiza al menos una ejecución de instrucciones:

203

Page 204: Programacion en Java

Do instrucciones

Loop While condición

Hace el bucle cero o más veces Hace el bucle al menos una vezDo Until condición instruccionesLoop

Do instruccionesLoop Until condición

2.4.2- For – Next.

Los bucles Do funcionan bien cuando no se sabe cuántas veces se necesitará ejecutar las instrucciones del bucle. Sin embargo, cuando se sabe que se van a ejecutar las instrucciones un número determinado de veces, es mejor elegir el bucle For…Next. A diferencia del bucle Do, el bucle For utiliza una variable llamada contador que incrementa o reduce su valor en cada repetición del bucle. La sintaxis es la siguiente:

For contador = iniciar To finalizar [Step incremento]

instrucciones

Next [contador]

Los argumentos contador, iniciar, finalizar e incremento son todos numéricos.

Nota: El argumento incremento puede ser positivo o negativo. Si incremento es positivo, iniciar debe ser menor o igual que finalizar o no se ejecutarán las instrucciones del bucle. Si incremento es negativo, iniciar debe ser mayor o igual que finalizar para que se ejecute el cuerpo del bucle. Si no se establece Step, el valor predeterminado de incremento es 1.

Al ejecutar el bucle For, Visual Basic:

1. Establece contador al mismo valor que iniciar.

2. Comprueba si contador es mayor que finalizar. Si lo es, Visual Basic sale del bucle.

(Si incremento es negativo, Visual Basic comprueba si contador es menor que finalizar.)

3. Ejecuta instrucciones.

4. Incrementa contador en 1 o en instrucciones, si se especificó.

5. Repite los pasos 2 a 4.

Este código imprime los nombres de todas las fuentes de pantalla disponibles:

Private Sub Form_Click ()

Dim I As Integer

For i = 0 To Screen.FontCount

204

Page 205: Programacion en Java

Print Screen.Fonts(i)

Next

End Sub

En la aplicación de ejemplo VCR, el procedimiento HighlightButton utiliza un bucle For...Next para pasar por la colección de controles del formulario VCR y mostrar el control Shape apropiado:

Sub HighlightButton(MyControl As Variant)

Dim i As Integer

For i = 0 To frmVCR.Controls.Count - 1

If TypeOf frmVCR.Controls(i) Is Shape Then

If frmVCR.Controls(i).Name = MyControl Then

frmVCR.Controls(i).Visible = True

Else

frmVCR.Controls(i).Visible = False

End If

End If

Next

End Sub

2.4.3- If – Else – End If.

Use la estructura If...Then para ejecutar una o más instrucciones basadas en una condición. Puede utilizar la sintaxis de una línea o un bloque de varias líneas:

If condición Then instrucción

If condición Then

instrucciones

End If

Condición normalmente es una comparación, pero puede ser cualquier expresión que dé como resultado un valor numérico. Visual Basic interpreta este valor como True o False; un valor numérico cero es False y se considera True cualquier valor numérico distinto de cero. Si condición

205

Page 206: Programacion en Java

es True, Visual Basic ejecuta todas las instrucciones que siguen a la palabra clave Then. Puede utilizar la sintaxis de una línea o de varias líneas para ejecutar una instrucción basada en una condición (estos dos ejemplos son equivalentes):

If cualquierFecha < Now Then cualquierFecha = Now

If cualquierFecha < Now Then

cualquierFecha = Now

End If

Observe que el formato de una única línea de If...Then no utiliza la instrucción End If. Si desea ejecutar más de una línea de código cuando condición sea True, debe utilizar la sintaxis de bloque de varias líneas If...Then...End If.

If cualquierFecha < Now Then

cualquierFecha = Now

Timer1.Enabled = False ' Desactiva el control Timer.

End If

If...Then...Else

Utilice un bloque If...Then...Else para definir varios bloques de instrucciones, uno de los cuales se ejecutará:

If condición1 Then

[bloque de instrucciones 1] [Else

[bloque de instrucciones n]]

End If

Visual Basic evalúa primero condición1. Si es False, Visual Basic ejecuta el bloque de instrucciones correspondientes a Else y después ejecuta el código que sigue a End If.

Por ejemplo, la aplicación podría realizar distintas acciones dependiendo del control en que se haya hecho clic de una matriz de controles de menú:

Private Sub mnuCut_Click (Index As Integer)

If Index = 0 Then ' Comando Cortar.

CopyActiveControl

ClearActiveControl

206

Page 207: Programacion en Java

Else ' Comando Pegar.

PasteActiveControl

End If

End Sub

2.4.4- Select - Case

Visual Basic proporciona la estructura Select Case como alternativa a If...Then...Else para ejecutar selectivamente un bloque de instrucciones entre varios bloques de instrucciones. La instrucción Select Case ofrece posibilidades similares a la instrucción If...Then...Else, pero hace que el código sea más legible cuando hay varias opciones.

La estructura Select Case funciona con una única expresión de prueba que se evalúa una vez solamente, al principio de la estructura. Visual Basic compara el resultado de esta expresión con los valores de cada Case de la estructura. Si hay una coincidencia, ejecuta el bloque de instrucciones asociado a ese Case:

Select Case expresión_prueba

[Case lista_expresiones1

[bloque de instrucciones 1]]

[Case lista_expresiones2

[bloque de instrucciones 2]] . . . [Case Else

[bloque de instrucciones n]]

End Select

Cada lista_expresiones es una lista de uno o más valores. Si hay más de un valor en una lista, se separan los valores con comas. Cada bloque de instrucciones contiene cero o más instrucciones. Si más de un Case coincide con la expresión de prueba, sólo se ejecutará el bloque de instrucciones asociado con la primera coincidencia. Visual Basic ejecuta las instrucciones de la cláusula (opcional) Case Else si ningún valor de la lista de expresiones coincide con la expresión de prueba.

Por ejemplo, suponga que agrega otro comando al menú Edición en el ejemplo If...Then...Else. Podría agregar otra cláusula ElseIf o podría escribir la función con Select Case:

Private Sub mnuCut_Click (Index As Integer)

Select Case Index

207

Page 208: Programacion en Java

Case 0 ' Comando Cortar.

CopyActiveControl ' Llama a procedimientos generales.

ClearActiveControl

Case 1 ' Comando Copiar.

CopyActiveControl

Case 2 ' Comando Borrar.

ClearActiveControl

Case 3 ' Comando Pegar.

PasteActiveControl

Case Else

frmFind.Show ' Muestra el cuadro de diálogo Buscar.

End Select

End Sub

Observe que la estructura Select Case evalúa una expresión cada vez al principio de la estructura.

3- Controles

3.1- Controles básicos

Vamos a ver los siguientes puntos:

• Introducción al uso de controles • Control TextBox. • Control Label • Control CommandButton • Control OptionButton • Realizacíon de una pequeña aplicación de ejemplo

Antes de empezar a conocer los controles básicos veamos cuales son sus características generales:

208

Page 209: Programacion en Java

• Propiedades:Todos los controles disponen de una serie de propiedades las cuales podemos cambiar al incluirlos en nuestras aplicaciones. Ejemplos de propiedades son el color, el tipo de letra, el nombre, el texto, etc...

• Metodos: Son procedimientos asociados a los controles, es decir, rutinas ya establecidas que podemos invocar desde nuestras aplicaciones para que se realice alguna operación sobre el control. Por ejemplo el control ListView ( la lista de archivos que aparece en el explorador de windows) dispone del método order que te ordena los datos aparecidos en la lista.

• Eventos: Son acciones que pueden ser motivadas por el propio usuario o por mismo sistema operativo. Ejemplos pueden ser el movimiento del ratón o hacer click sobre su botón. En Visual Basic digamos que se utiliza la programación orientada a eventos, lo cual es una de las diferencias más importantes respecto a la programación lineal de MS DOS. No necesitamos detectar cuando se ha producido un evento determinado, Windows lo detecta automáticamente. Los eventos ya estan definidos, son bastantes y cada control cuenta con los suyos propios, aunque son muy parecidos. Lo único que tendremos que hacer es asociar el código necesario al evento que necesitemos tratar.

TextBox

Mediante este control podremos realizar tanto la entrada como la salida de datos en nuestras aplicaciones.

No hace falta que indiquemos las coordenadas de la situación del formulario en pantalla, simplemente tendremos que marcar sobre el control de la caja de herramientas y dibujarlo con el tamaño que queramos en nuestro formulario.

PROPIEDADES

Las propiedades de las que dispone el control son las siguientes:(para obtener el cuadro de propiedades, seleccionar el control y pulsar F4 o pulsar con el boton derecho para obtener el menú contextual y marcar Propierties)

Text: Aquí indicamos el texto que aparecerá en el control. Podemos asignarle cualquier texto en tiempo de diseño o ejecución. También podemos tomar el texto que haya introducido el usuario para tratarlo durante la ejecución.

Name: Esta propiedad la tienen todos los controles, el nombre que viene por defecto en este caso Text1 y es el nombre con el que se conocerá el control cuando lo utilicemos en el código. En un mismo formulario no puede haber 2 controles con el mismo nombre. Conviene poner un nombre que represente la función que tiene el control en la aplicación para que el código quede más claro. Ejemplo, si en el textbox vamos a introducir la dirección de una persona podemos asignarle a esta propiedad el valor Dirección.

MultiLine: Permite que introduzcamos varias lineas de texto en el control en lugar de sólo una.

Alignment: Alineación que tendrá el texto dentro del control: izquierda, centro o derecha. Para que funcione la propiedad MultiLine debe estar con el valor true.

Locked: Si esta con valor true bloquea el control, es decir, el usuario no puede introducir ni modificar el texto que contenga. Nos puede servir para utilizar el control como salida de datos sin que el usuario pueda modificarlos por error.

209

Page 210: Programacion en Java

Otras propiedades que son comunes a la mayoria de los controles:

Backcolor: color de fondo.

Forecolor: color de letra.

Font: tipo y tamaño de letra.

METODOS

Recordemos que por métodos se entienten los procedimientos o funciones asociados a un control, los cuales nos permiten realizar ciertas operaciones útiles sobre dicho control: Ej. ordenar sus elementos, buscar un dato, etc..

Pues bien, los controles básicos que vamos a ver en este capítulo únicamente contienen métodos avanzados que no vamos a analizar por ahora, ya que son métodos que no se suelen utilizar. Más adelante cuando veamos otros tipos de controles estudiaremos cuales son los métodos que nos podrán servir. Si alguien está interesado en conocer todas las características de los controles puede hacerlo mirando en la ayuda que proporciona VB, haciendo click sobre cualquier control de la caja de herramientas y pulsando a continuación F1 obtendrá ayuda referente a ese control donde aparecerán todas sus propiedades, metodos y eventos.

EVENTOS

Los eventos son acciones que se pueden realizar en cualquier control: click, doble click, movimiento del ratón. A estos eventos se les puede asociar código para que se ejecute al producir el evento.

MouseMove: al mover el raton por encima del control.

Mousedown: al pulsar cualquier boton del raton

Change: al cambiar el contenido del control

Click: al hacer click con el botón izquierdo del ratón sobre el control

Doubleclick: al hacer doble click con el con el botón izquierdo del ratón sobre el control

Getfocus: este evento se activa cuando el control recibe el enfoque, es decir, cuando se activa el control en tiempo de ejecución para introducir datos en él o realizar alguna operación.

Lostfocus: Es el contrario del anterior evento, se activa cuando el control pierde el enfoque, es decir, se pasa a otro control para seguir introduciendo datos.

EJEMPLO

Vamos a probar el uso del control TextBox mediante un pequeño ejemplo en el que teniendo un único control de este tipo en un formulario, lo programaremos de forma que al pasar el ratón sobre el control (evento mousemove) aparecerá en el formulario el texto que contenga.

210

Page 211: Programacion en Java

Observamos que al situar el control en el formulario aparece por defecto el texto Text1. Para que no aparezca ese texto al ejecutar la aplicación, debemos cambiar la propiedad Text pulsando F4 y colocar el texto que queramos o no colocar nada.

Lo que queremos hacer es que cada vez que movamos el raton por el control aparezca su contenido en el formulario. Entonces lo que habrá que hacer abrir la ventana de código, seleccionando el control y pulsando F7, o con el botón derecho del ratón y la opción View code del menú contextual. Este proceso nos llevará al cuadro de la imagen siguiente.

Lo que tendremos que hacer es seleccionar el evento que necesitemos de la sección Proc, en nuestro caso mousemove y a continuación teclear el codigo correspondiente: La instrucción print visualiza un texto en el formulario y si le ponemos text1.text le decimos que nos muestre la propiedad Text del control Text1 que ese será el nombre que tendrá el control por defecto si no lo hemos cambiado en la propiedad name.

Al ejecutar esta pequeña aplicación pulsando F5 observaremos como aparece en el formulario lo que hayamos tecleado en el control cada vez que movemos el raton sobre el Textbox.

Podemos modificar el programa para que responda a cualquier otro evento sin más que seleccionarlo en la sección Proc e introduciendo el codigo que sea necesario.

Label

Este control es también uno de los más utilizados, aunque su utilidad queda restringida a la visualización de datos en el mismo, no permitiendo la introducción de datos por parte del usuario.

La forma de utilizarlo es similar a la del control anterior, dibujar el control en el formulario con el tamaño que queramos y asignarle un texto en tiempo de diseño o de ejecución esta vez sin utilizar la propiedad text puesto que no la incorpora, sino utilizando la propiedad caption.

211

Page 212: Programacion en Java

Este control sirve para mostrar mensajes en nuestro formulario que orienten al usuario sobre la utilidad de los demás controles que tengamos en la aplicación o para indicarnos acciones que podemos realizar. En el ejemplo anterior donde aparecía un textbox en el formulario, hubiera quedado mejor con un mensaje aclaratorio contenido en un control label:

PROPIEDADES

Caption: Es el texto que contendrá el control.

Alignment: Alineación del texto contenido en el control, no necesita que esté activada ninguna otra propiedad.

BorderStyle: Si queremos que aparezca un borde alrededor del control activaremos esta propiedad.

Para este control no se suelen utilizar los eventos ya que su contenido suele cambiar poco a lo largo de la ejecución de la aplicación. De todas formas los eventos son casi los mismos del control textbox excepto que no dispone de los eventos GetFocus y LostFocus ya que a este control no se le puede dar el enfoque.

En la parte final de este capitulo veremos un ejemplo donde se muestra el funcionamiento de todos los controles que vamos a ir viendo.Por ahora a ver si conseguis que ahora el mensaje no aparezca en el formulario sino en un segundo label situado en el formulario, dejando un control label que muestre el mensaje aclaratorio que hemos visto antes.

CommandButton

Este control es el típico botón que aparece en todas las aplicaciones y que al hacer click sobre él nos permite realizar alguna operación concreta, normalmente Aceptar o Cancelar. Aunque según el código que le asociemos podremos realizar las operaciones que queramos.

En el ejemplo anterior podemos añadir un control de este tipo para salir de la aplicación sin tener pulsar sobre la equis de la esquina superior derecha.

Pero sólo con introducir un control de este tipo con el texto salir que se introduce a traves de la propiedad caption no basta. Habrá que asociarle un código que nos permita salir de la aplicación

212

Page 213: Programacion en Java

en el evento adecuado. Y el evento por excelencia de este control es click. Así pues accederemos al código del control y la sentencia nos permitirá salir de la aplicación es End, simplemente tecleamos esa palabra en el evento click y comprobar que realmente finalizaremos nuestra aplicación al pulsar sobre dicho botón.

PROPIEDADES

Caption: Aqui pondremos el letrero que queremos que apaezca en el botón: aceptar, cancelar, salir, etc...

Enabled: Esta es una nueva propiedad, cuando su valor es true el botón funciona normalmente, cuando su valor es false el boton se encuentra desactivado, no responde a los eventos producidos sobre él y el texto aparece en un gris claro advirtiendonos de su estado. Podemos utilizar esta propiedad para activar o desactivar un boton dependiendo del estado de otros controles. Por ejemplo, en un boton Aceptar, no activarlo hasta que se haya introducido una cantidad en un control textbox, ya que ese botón nos calculará el IVA de la cantidad.

EVENTOS

Click: Es el evento tipico de este control y el que más se utiliza.

MouseMove: Como sabemos detecta el movimiento del raton sobre el control. Puede servir para que aparezca un mensaje en un control Label que nos aporte información sobre la utilidad del control ampliando el texto que hayamos colocado como caption del commandbutton.

OptionButton

Este control nos permite elegir una opción entre varias de las que se nos plantean. Cada opción será un control optionbutton diferente.

Facilita la introducción de datos por parte del usuario:

De todas las opciones que se nos ofrece, en este caso los 4 colores, sólo podremos activar una. Si activamos cualquier otra opción, se desactivará automáticamente la última que teníamos activada.

213

Page 214: Programacion en Java

El marco que está alrededor de los 4 controles optionbutton se trata del control Frame , es opcional, aunque es conviente colocarlo siempre que hagamos uso de las opciones. No sólo por motivos de presentación sino porque de esta manera podremos establecer grupos de controles optionbutton independientes en los que en cada grupo sólo pueda haber una opcioón activada a la vez. También, al mover el marco se moverán los controles incluidos en él facilitándonos las modificaciones.

Para que los controles Optionbutton queden englobados dentro de un control Frame, primero tendremos que colocar el control Frame en el formulario con el tamaño adecuado y despues ir colocando los controles Optionbutton dentro del Frame.

Del control Frame la única propiedad que nos interesará es caption, que es el texto que aparecerá en el encabezado, en el ejemplo anterior: colores.

PROPIEDADES DE OPTIONBUTTON

Caption: El texto que aparecerá al lado del control: Rojo, verde, etc...

Value: Es el valor que tendrá el control: True si se encuentra activado y False si no lo está. Para comprobar que opcion ha activado el usuario comprobaremos el estado de esta propiedad.

Alignment: Alineación del texto respecto al control: Left Justify: el control aparece a la izquierda del texto. Es el ejemplo anterior.Right Justify: el control aparece a la derecha del texto.

Los eventos del control son los mismos que en anteriores controles, aunque no se suele asociar código a los eventos de este tipo de controles, sino únicamente conocer el valor que tienen: true o false.

3.1.1- APLICACION DE EJEMPLO

Para practicar con los controles que hemos visto vamos a realizar una pequeña aplicación que consistirá en realizar con 2 números que introduzcamos, una operación que seleccionemos y mostrar el resultado.

El formulario donde estarán todos los controles es el siguiente:

La propiedad Caption de cada uno de los controles es la que se muestra en el formulario.

214

Page 215: Programacion en Java

He modificado la propiedad Name de cada control para que al utilizarlos desde el codigo sepamos cual es el control con el que trabajamos:

• Los controles TextBox tienen los nombres: Num1, Num2 y Resul.• Los controles Optionbutton tienen cada uno de ellos el mismo nombre que su caption• Los controles CommandButton tienen los nombres: Calcular, Limpiar y Salir.• A los controles Label y al Frame no have falta cambiarles el nombre.

Lo que habrá que hacer ahora es asociar codigo a cada uno de los botones que es de donde se van a realizar las operaciones:

• Para el botón Calcular que es el que nos mostrará el resultado según la operación seleccionada, he utilizado la instrucción If Then Else que vimos en el capítulo anterior:

• El botón Limpiar Datos nos va a servir para borrar de una forma rápida los datos introducidos por el usuario y el resultado preparando los controles para introducir nuevos datos. El código que tendremos que introducir es muy simple:

215

Page 216: Programacion en Java

El botón Salir únicamente contendrá la sentencia End.

4 -Formularios.

Los formularios tienen sus propios eventos, propiedades y métodos con los que se puede controlar su apariencia y comportamiento.

El primer paso para diseñar un formulario consiste en establecer sus propiedades. Puede establecer las propiedades de un formulario en tiempo de diseño en la ventana Propiedades o en tiempo de ejecución, escribiendo código.

Nota En tiempo de diseño, que es cualquier momento mientras está desarrollando una aplicación en el entorno de Visual Basic, se trabaja con formularios y controles, se establecen propiedades y se escribe código para los eventos. Tiempo de ejecución es cualquier momento mientras se ejecuta realmente la aplicación y se interactúa con ella como lo haría un usuario.

4.1- Estableciendo las Propiedades más importantes de los formularios.

Muchas propiedades de un formulario afectan a su apariencia física. La propiedad Caption determina el texto que muestra la barra de título del formulario y la propiedad Icon establece el icono que aparece cuando se minimiza un formulario. Las propiedades MaxButton y MinButton determinan si el formulario se puede maximizar o minimizar. Cambiando la propiedad BorderStyle puede controlar el comportamiento de cambio de tamaño del formulario.

Las propiedades Height y Width determinan el tamaño inicial de un formulario, mientras que las propiedades Left y Top determinan la ubicación del formulario en relación con la esquina superior izquierda de la pantalla. Con la propiedad WindowState puede establecer si el formulario se inicia en estado maximizado, minimizado o normal.

La propiedad Name establece el nombre con el que hará referencia al formulario en el código. De forma predeterminada, cuando se agrega un formulario por primera vez a un proyecto, su nombre es Form1, Form2, etc. Es conveniente establecer la propiedad Name a algo más significativo, como “frmEntry” para un formulario de entrada de pedidos.

La mejor manera de familiarizarse con las numerosas propiedades de los formularios es experimentar. Cambie algunas propiedades de un formulario en la ventana Propiedades y ejecute la aplicación para ver su efecto. Puede aprender más sobre cada propiedad si la selecciona y presiona F1 para ver Ayuda contextual.

4.2- Eventos y Métodos de los formularios.

Como objetos que son, los formularios pueden ejecutar métodos y responder a eventos.

El evento Resize de un formulario se desencadena siempre que se cambia el tamaño de un formulario, ya sea por una acción del usuario o a través del código. Esto permite realizar acciones como mover o cambiar el tamaño de los controles de un formulario cuando han cambiado sus dimensiones.

El evento Activate se produce siempre que un formulario se convierte en el formulario activo; el evento Deactivate se produce cuando otro formulario u otra aplicación se convierte en activo. Estos eventos son adecuados para iniciar o finalizar acciones del formulario. Por ejemplo, en el evento Activate podría escribir código para resaltar el texto de un determinado cuadro de texto; con el evento Deactivate podría guardar los cambios efectuados en un archivo o en una base de datos.

216

Page 217: Programacion en Java

Para hacer visible un formulario se invoca el método Show:

Form2.Show

Para descargar un formulario (cerrarlo), se invoca al método Unload:

Form2.Unload

Unload Me ‘Me significa el Formulario activo.

Para ocultar un formulario (pero dejarlo activo en memoria) se invoca al método Hide:

Form2.Hide

Invocar el método Show tiene el mismo efecto que establecer a True la propiedad Visible del formulario.

Muchos métodos de un formulario implican texto o gráficos. Los métodos Print, Line, Circle y Refresh son útiles para imprimir o dibujar directamente en la superficie de un formulario.

4.3- Establecer el formulario de arranque de la aplicación.

De forma predeterminada, el primer formulario de la aplicación es el formulario inicial. Cuando la aplicación inicia la ejecución, se presenta este formulario (el primer código que se ejecuta es el del evento Form_Initialize de dicho formulario). Si quiere presentar un formulario diferente cuando se inicie la aplicación, debe cambiar el formulario inicial.

Para cambiar el formulario inicial

1. En el menú Proyecto, elija Propiedades del proyecto.

2. Elija la ficha General.

3. En el cuadro de lista Objeto inicial, seleccione el formulario que desee que sea el nuevo formulario inicial.

4. Elija Aceptar.

Inicio sin formulario inicial

Algunas veces puede desear que la aplicación se inicie sin cargar ningún formulario. Por ejemplo, puede que desee ejecutar código que cargue un archivo de datos y después presentar uno de entre varios formularios, según el contenido de dicho archivo. Puede hacerlo creando un procedimiento Sub llamado Main en un módulo estándar, como en el siguiente ejemplo:

Sub Main()

Dim intStatus As Integer

' Llamar a un procedimiento de función para comprobar el estado

217

Page 218: Programacion en Java

' del usuario.

intStatus = GetUserStatus

' Mostrar un formulario inicial distinto según el estado.

If intStatus = 1 Then

frmMain.Show

Else

frmPassword.Show

End If

Este procedimiento tiene que ser un procedimiento Sub y no puede estar en un módulo de formulario. Para establecer el procedimiento Sub Main como objeto inicial, en el menú Proyecto elija Propiedades del proyecto, seleccione la ficha General y seleccione Sub Main en el cuadro Objeto inicial.

5 -Combo Box (lista combo)

Un control ComboBox combina las características de un control TextBox y un control ListBox; los usuarios pueden introducir información en la parte del cuadro de texto o seleccionar un elemento en la parte de cuadro de lista del control.

Para agregar o eliminar elementos en un control ComboBox, use el método AddItem o RemoveItem. Establezca las propiedades List, ListCount y ListIndex para permitir a un usuario tener acceso a los elementos de un control ComboBox. Como alternativa, puede agregar elementos a la lista mediante la propiedad List en tiempo de diseño.

Nota Un evento Scroll ocurrirá en un control ComboBox sólo cuando se desplace el contenido de la parte desplegable del ComboBox, no cada vez que cambie el contenido del ComboBox. Por ejemplo, si la parte desplegable de un ComboBox contiene cinco elementos y el elemento superior está resaltado, no ocurrirá un evento Scroll hasta que presione seis veces la flecha hacia abajo (o una vez la tecla AV PÁG). Después de eso, ocurrirá un evento Scroll por cada pulsación de la tecla de flecha hacia abajo. Sin embargo, si después presiona la tecla de flecha hacia arriba, no ocurrirá un evento Scroll hasta que presione seis veces la tecla de flecha hacia arriba (o una vez la tecla RE PÁG). Después de eso, cada vez que presione la tecla de flecha hacia arriba se producirá un evento Scroll.

Eventos:

Evento Change Evento LostFocus

Evento Click Evento OLECompleteDrag

Evento DblClick Evento OLEDragDrop

Evento DragDrop Evento OLEDragOver

218

Page 219: Programacion en Java

Evento DragOver Evento OLEGiveFeedback

Evento DropDown Evento OLESetData

Evento GotFocus Evento OLEStartDrag

Eventos KeyDown y KeyUp Evento Scroll

Evento KeyPress

Métodos:

Método AddItem Método Refresh

Método Clear (Clipboard, ComboBox, ListBox) Método RemoveItem

Método Drag Método SetFocus

Método Move Método ShowWhatsThis

Método OLEDrag Método ZOrder

Propiedades:

Propiedad Appearance Propiedad ListIndex

Propiedades BackColor y ForeColor Propiedad Locked

Propiedad Container Propiedad MouseIcon

Propiedad DataChanged Propiedad MousePointer

Propiedad DataField Propiedad Name

Propiedad DragIcon Propiedad NewIndex

Propiedad DragMode Propiedad OLEDragMode

Propiedad Enabled Propiedad OLEDropMode

Propiedad Font Propiedad Parent

Propiedades FontBold, FontItalic, FontStrikethru y FontUnderline

Propiedad FontName Propiedades SelLength, SelStart y SelText (Controles ActiveX)

Propiedad FontSize Propiedad Sorted

Propiedades Height y Width Propiedad Style

219

Page 220: Programacion en Java

Propiedad HelpContextID Propiedad TabIndex

Propiedad hWnd Propiedad TabStop

Propiedad Index (Control Array) Propiedad Tag

Propiedad IntegralHeight Propiedad Text

Propiedad ItemData Propiedad ToolTipText

Propiedades Left y Top Propiedad TopIndex

Propiedad List Propiedad Visible

Propiedad ListCount Propiedad WhatsThisHelpID

Propiedades SelLength, SelStart y SelText

6- List Box (lista).

Un control ListBox muestra una lista de elementos entre los cuales el usuario puede seleccionar uno o más. Si el número de elementos supera el número que puede mostrarse, se agregará automáticamente una barra de desplazamiento al control ListBox.

Si no se selecciona ningún elemento, el valor de la propiedad ListIndex será -1. El primer elemento de la lista es ListIndex 0 y el valor de la propiedad ListCount siempre es uno más que el mayor valor de ListIndex.

Para agregar o eliminar elementos de un control ListBox, use el método AddItem o RemoveItem. Establezca las propiedades List, ListCount y ListIndex para permitir que un usuario tenga acceso a elementos del ListBox. También puede agregar elementos a la lista mediante la propiedad List en tiempo de diseño.

Eventos

Evento Click Eventos MouseDown y MouseUp

Evento DblClick Evento MouseMove

Evento DragDrop Evento OLECompleteDrag

Evento DragOver Evento OLEDragDrop

Evento GotFocus Evento OLEDragOver

Evento ItemCheck Evento OLEGiveFeedback

Eventos KeyDown y KeyUp Evento OLESetData

Evento KeyPress Evento OLEStartDrag

220

Page 221: Programacion en Java

Evento LostFocus Evento Scroll

Métodos

Método AddItem Método Refresh

Método Clear (Clipboard, ComboBox, ListBox) Método RemoveItem

Método Drag Método SetFocus

Método Move Método ShowWhatsThis

Método OLEDrag Método ZOrder

Propiedades

Propiedad Appearance Propiedad MousePointer

Propiedades BackColor y ForeColor Propiedad MultiSelect

Propiedad Columns (ListBox) Propiedad Name

Propiedad Container Propiedad NewIndex

Propiedad DataChanged Propiedad OLEDragMode

Propiedad DataField Propiedad OLEDropMode

Propiedad DataSource Propiedad Parent

Propiedad DragIcon Propiedad SelCount

Propiedad DragMode Propiedad Selected

Propiedad Enabled Propiedad Sorted

Propiedad Font Propiedad Style

Propiedades FontBold, FontItalic, FontStrikethru y FontUnderline

Propiedad FontName Propiedad TabStop

Propiedad FontSize Propiedad Tag

Propiedades Height y Width Propiedad Text

Propiedad HelpContextID Propiedad ToolTipText

Propiedad hWnd Propiedad TopIndex

Propiedad Index (Control Array) Propiedad Visible

221

Page 222: Programacion en Java

Propiedad ItemData Propiedad WhatsThisHelpID

Propiedades Left y Top Propiedad TabIndex

Propiedad List Propiedad ListCount

Propiedad ListIndex Propiedad MouseIcon

7- Timer (cronómetro)

Un control Timer puede ejecutar código a intervalos periódicos produciendo un evento Timer.

El control Timer, invisible para el usuario, resulta útil para el procesamiento de fondo.

No puede establecer la propiedad Enabled de un Timer para una selección múltiple de controles que no sean controles Timer.

No existe ningún límite práctico en cuanto al número de controles Timer activos que puede tener en Visual Basic 5.0 ejecutándose en Windows 95 o en Windows NT.

Eventos:

Evento Timer

Propiedades:

Propiedad Enabled Propiedad Name

Propiedad Index (Control Array) Propiedad Parent

Propiedad Interval Propiedad Tag

Propiedades Left y Top

8- Shape (figura).

Shape es un control gráfico que se muestra como un rectángulo, un cuadrado, una elipse, un círculo, un rectángulo redondeado o un cuadrado redondeado.

Utilice controles Shape en tiempo de diseño en lugar de o además de invocar los métodos Circle y Line en tiempo de ejecución. Puede dibujar un control Shape en un contenedor, pero no puede actuar como contenedor. El efecto de establecer la propiedad BorderStyle depende del valor de la propiedad BorderWidth. Si BorderWidth no es 1 y BorderStyle no es 0 ó 6, BorderStyle se establece a 1.

Métodos:

Método Move Método ZOrder

Método Refresh

222

Page 223: Programacion en Java

Propiedades:

Propiedades BackColor y ForeColor Propiedades Height y Width

Propiedad BackStyle Propiedad Index (Control Array)

Propiedad BorderColor Propiedades Left y Top

Propiedad BorderStyle Propiedad Name

Propiedad BorderWidth Propiedad Parent

Propiedad Container Propiedad Shape

Propiedad DrawMode Propiedad Tag

Propiedad FillColor Propiedad Visible

Propiedad FillStyle

9- Line (línea).

Line es un control gráfico que se muestra como una línea horizontal, vertical o diagonal.

Puede utilizar un control Line en tiempo de diseño para dibujar líneas en formularios. En tiempo de ejecución puede utilizar un control Line en lugar del método Line, o además de él. Las líneas dibujadas con el control Line permanecen en el formulario aunque la propiedad AutoRedraw sea False. Los controles Line pueden mostrarse en formularios, en cuadros de imagen y en marcos. No puede utilizar el método Move para mover un control Line en tiempo de ejecución, pero sí se puede mover o cambiar de tamaño alterando sus propiedades X1, X2, Y1 e Y2. El efecto de establecer la propiedad BorderStyle depende del valor de la propiedad BorderWidth. Si BorderWidth no es 1 y BorderStyle no es 0 ó 6, BorderStyle se establecerá a 1.

Métodos:

Método Refresh

Método ZOrder

Propiedades:

Propiedad BorderColor Propiedad Name

Propiedad BorderStyle Propiedad Parent

Propiedad BorderWidth Propiedad Tag

Propiedad Container Propiedad Visible

Propiedad DrawMode Propiedades X1, Y1, X2 y Y2

223

Page 224: Programacion en Java

Propiedad Index (Control Array)

10- Image (imagen)

Utilice el control Image para mostrar un gráfico. Un control Image puede mostrar un gráfico desde un mapa de bits, un icono o un metarchivo, así como un metarchivo mejorado, un archivo JPEG o archivos GIF.

El control Image utiliza menos recursos del sistema y actualiza con más rapidez que un control PictureBox, pero sólo admite un subconjunto de las propiedades, los eventos y los métodos de PictureBox. Use la propiedad Stretch para determinar si el gráfico se escala para ajustarse al control o viceversa. Aunque puede colocar un control Image dentro de un contenedor, un control Image no puede actuar como contenedor.

Eventos:

Evento Click Evento OLECompleteDrag

Evento DblClick Evento OLEDragDrop

Evento DragDrop Evento OLEDragOver

Evento DragOver Evento OLEGiveFeedback

Eventos MouseDown y MouseUp Evento OLESetData

Evento MouseMove Evento OLEStartDrag

Métodos:

Método Drag Método Refresh

Método Move Método ShowWhatsThis

Método OLEDrag Método ZOrder

Propiedades:

Propiedad Appearance Propiedad MouseIcon

Propiedad BorderStyle Propiedad MousePointer

Propiedad Container Propiedad Name

Propiedad DataChanged Propiedad OLEDragMode

Propiedad DataField Propiedad OLEDropMode

Propiedad DataSource Propiedad Parent

Propiedad DragIcon Propiedad Picture

224

Page 225: Programacion en Java

Propiedad DragMode Propiedad Stretch

Propiedad Enabled Propiedad Tag

Propiedades Height y Width Propiedad ToolTipText

Propiedad Index (Control Array) Propiedad Visible

Propiedades Left y Top Propiedad WhatsThisHelpID

11- Data (acceso a bases de datos)

Proporciona acceso a datos almacenados en bases de datos mediante uno de los tres tipos de objetos Recordset. El control Data le permite desplazarse de un registro a otro, así como presentar y manipular datos de los registros en controles enlazados. Sin un control Data los controles enlazados a datos (vinculados) de un formulario no pueden tener acceso a los datos automáticamente.

Puede realizar la mayoría de las operaciones de acceso a datos utilizando el control Data sin escribir código. Los controles enlazados a datos vinculados a un control Data presentan automáticamente los datos de uno o varios campos del registro actual o, en algunos casos, de un conjunto de registros situado a ambos lados del registro actual. El control Data realiza todas sus operaciones sobre el registro actual.

Si el control Data recibe instrucciones para desplazarse a un registro diferente, todos los controles enlazados pasan automáticamente los posibles cambios al control Data para que los guarde en la base de datos. Después, el control Data se desplaza al registro solicitado y pasa los datos del registro actual a los controles enlazados, en los que se presentan.

El control Data administra automáticamente una serie de contingencias entre las que se incluyen los conjuntos de registros vacíos, la inserción de nuevos registros, la modificación y actualización de registros existentes, y la administración de ciertos tipos de errores. Sin embargo, en aplicaciones más sofisticadas es necesario interceptar algunas condiciones de error que el control Data no puede administrar. Por ejemplo, si el motor de base de datos Microsoft Jet tiene un problema al tener acceso al archivo de base de datos, no tiene el permiso adecuado o no puede ejecutar la consulta, se producirá un error interceptable. Si el error se produce antes de que se inicien los procedimientos de la aplicación o se trata de errores internos, se desencadenará el evento Error.

12- Controles enlazados

Los controles DBList, DBCombo, DBGrid y MSFlexGrid son capaces de administrar conjuntos de registros cuando están enlazados a un control Data. Todos estos controles permiten presentar o manipular varios registros a la vez.

Los controles incorporados Picture, Label, TextBox, CheckBox, Image, OLE, ListBox y ComboBox también son controles enlazados a datos y se pueden enlazar a un único campo de un Recordset administrado por un control Data. En las ediciones Profesional y Empresarial se encuentran disponibles otros controles enlazados a datos como MaskedEdit y RichTextBox; otros proveedores también ofrecen controles adicionales.

12.1 Funcionamiento

225

Page 226: Programacion en Java

Una vez iniciada la aplicación, Visual Basic utiliza las propiedades del control Data para abrir la base de datos seleccionada, crear un objeto Database y crear un objeto Recordset. Las propiedades Database y Recordset del control Data hacen referencia a los objetos Database y Recordset recién creados, que se pueden manipular de forma independiente del control Data, con o sin controles enlazados. El control Data se inicializa antes del evento Form_Load inicial del formulario en el que se encuentra. Si se producen errores durante esta fase de inicialización, se produce un error no interceptable.

Cuando Visual Basic utiliza el motor de base de datos Jet para crear un Recordset, no se pueden producir otras operaciones o eventos de Visual Basic hasta que se termine la operación. Sin embargo, otras aplicaciones basadas en Windows pueden seguir en ejecución mientras se está creando el Recordset. Si el usuario presiona CTRL+INTER mientras el motor Jet está generando un Recordset, la operación termina, se produce un error interceptable y la propiedad Recordset del control Data se establece a Nothing. En tiempo de diseño, la segunda vez que se presiona CTRL+INTER hace que Visual Basic presente la ventana

Depuración.

Puede manipular el control Data con el mouse (ratón), desplazándose de un registro a otro, o al principio o al final del Recordset. Las propiedades EOFAction y BOFAction determinan lo que ocurre cuando el usuario se desplaza al principio o al final de un Recordset con el mouse. No puede establecer el enfoque en un control Data.

12.2 Validación

El evento Validate y la propiedad DataChanged se utilizan para realizar comprobaciones de última hora sobre los registros que se van a escribir en la base de datos.

12.3 Objetos de acceso a datos

En los procedimientos puede utilizar los objetos de acceso a datos Database y Recordset creados por el control Data. Cada objeto Database y Recordset tiene sus propias propiedades y métodos, y puede escribir procedimientos que utilicen dichas propiedades y métodos para manipular sus datos.

Por ejemplo, el método MoveNext de un objeto Recordset desplaza el registro actual al siguiente registro dentro del Recordset. Para invocar este método, podría utilizar el código siguiente:

Data1.Recordset.MoveNext

El control Data puede tener acceso a cualquiera de los tres tipos de objetos Recordset del motor Jet versión 3.0. Si no selecciona el tipo de conjunto de registros, se crea un Recordset de tipo dynaset.

En muchos casos, el tipo predeterminado y la configuración del objeto Recordset creado son muy ineficientes. Es decir, puede que no necesite un cursor actualizable totalmente desplazable de conjunto de claves para tener acceso a los datos. Por ejemplo, un Recordset de tipo snapshot, de sólo lectura y unidireccional se crearía con más rapidez que el cursor predeterminado. Asegúrese de elegir el tipo más eficiente, así como las propiedades Exclusive, Options y ReadOnly adecuadas a su situación.

Para seleccionar un tipo de Recordset específico, establezca la propiedad RecordsetType del control Data a:

226

Page 227: Programacion en Java

RecordsetType Valor Constante

Table 0 vbRSTypeTable

Dynaset 1 vbRSTypeDynaset (Predeterminado)

Snapshot 2 vbRSTypeSnapshot

12.4 Ediciones Profesional y Empresarial

En lo que concierne al acceso a datos, la principal diferencia entre las ediciones de Aprendizaje, Profesional y Empresarial de Visual Basic es la capacidad de crear nuevos objetos de acceso a datos. En la Edición estándar no puede declarar (con la palabra clave Dim) variables como objetos de acceso a datos dentro del código. Esto significa que sólo el control Data puede crear objetos Database y Recordset.

En las ediciones Profesional y Empresarial de Visual Basic versión 5.0 puede crear un objeto Recordset y asignarlo a la propiedad Recordset de un control Data. Cualquier control enlazado que esté conectado al control Data permitirá manipular los registros del Recordset que ha creado. Asegúrese de que las propiedades DataField de los controles enlazados estén establecidas a nombres de campo válidos dentro del nuevo Recordset.

12.5 Consultas almacenadas

Otra opción importante al utilizar el control Data es la posibilidad de ejecutar consultas almacenadas. Si antes ha creado un objeto QueryDef, el control Data puede ejecutarlo y crear un Recordset mediante las propiedades SQL, Connect y otras del objeto QueryDef. Para ejecutar un QueryDef, establezca la propiedad RecordSource del control Data al nombre del QueryDef y utilice el método Refresh.

Si el QueryDef almacenado contiene parámetros, tiene que crear el Recordset y pasarlo al control Data.

12.6 Tratamiento de BOF/EOF

El control Data también puede administrar lo que ocurre cuando se encuentre con un Recordset sin registros. Modificando la propiedad EOFAction puede programar el control Data para que pase al modo AddNew de forma automática.

Puede programar el control Data para que se ajuste automáticamente a la parte superior o inferior del formulario primario utilizando la propiedad Align. En cualquiera de los casos, el control Data cambia de tamaño horizontalmente para llenar todo el ancho de su formulario primario, siempre que éste cambie de tamaño.

Eventos:

Evento DragDrop Evento OLEDragOver

Evento DragOver Evento OLEGiveFeedback

Evento Error Evento OLESetData

227

Page 228: Programacion en Java

Eventos MouseDown, MouseUp Evento OLEStartDrag

Evento MouseMove Evento Reposition

Evento OLECompleteDrag Evento Resize

Evento OLEDragDrop Evento Validate

Métodos

Método Drag Método ShowWhatsThis

Método Move Método UpdateControls

Método OLEDrag Método UpdateRecord

Método Refresh Método ZOrder

Propiedades

Propiedad Align Propiedad FontSize

Propiedad Appearance Propiedades Height, Width

Propiedades BackColor, ForeColor Propiedad Index (Matriz de controles)

Propiedad BOFAction, EOFAction Propiedades Left, Top

Propiedad Caption Propiedad MouseIcon

Propiedad Connect Propiedad MousePointer

Propiedad Database Propiedad Name

Propiedad DatabaseName Propiedad OLEDropMode

Propiedad DefaultCursorType Propiedad Options

Propiedad DefaultType Propiedad Parent

Propiedad DragIcon Propiedad ReadOnly (Aceso de datos)

Propiedad DragMode Propiedad Recordset

Propiedad EditMode Propiedad RecordsetType

Propiedad Enabled Propiedad RecordSource

Propiedad Exclusive Propiedad Tag

Propiedad Font Propiedad ToolTipText

228

Page 229: Programacion en Java

Propiedades FontBold, FontItalic, FontStrikethru, FontUnderline

Propiedad FontName Propiedad WhatsThisHelpID

Propiedad Visible

229

Page 230: Programacion en Java

13 -Eventos más importantes de los controles estándares.

13.1-Change

Aplicable a:

Control ComboBox, Controles HScrollBar y VScrollBar, Control Label, Control PictureBox, Control TextBox

Indica que el contenido de un control ha cambiado. Cómo y cuándo ha ocurrido este evento varía según el control:

• ComboBox: cambia el texto de la parte de cuadro de texto del control. Ocurre sólo si la propiedad Style está establecida a 0 (Dropdown Combo) o 1 (Simple Combo) y el usuario cambia el texto o usted cambia la configuración de la propiedad Text mediante código.

• DirListBox: cambia el directorio seleccionado. Ocurre cuando el usuario hace doble clic en un nuevo directorio o cuando usted cambia la configuración de la propiedad Path mediante código.

• DriveListBox: cambia la unidad seleccionada. Ocurre cuando el usuario selecciona una nueva unidad o cuando usted cambia la configuración de la propiedad Drive mediante código.

• HScrollBar y VScrollBar (barras de desplazamiento horizontal y vertical): mueven la parte de cuadro de desplazamiento de la barra de desplazamiento. Ocurre cuando el usuario desplaza o cuando usted cambia la configuración de la propiedad Value mediante código.

• Label: cambia el contenido del control Label. Ocurre cuando un vínculo DDE actualiza los datos o cuando usted cambia la configuración de la propiedad Caption mediante código.

• PictureBox: cambia el contenido del control PictureBox. Ocurre cuando un vínculo DDE actualiza los datos o cuando usted cambia la configuración de la propiedad Picture mediante código.

• TextBox: cambia el contenido del cuadro de texto. Ocurre cuando un vínculo DDE actualiza los datos, cuando un usuario cambia el texto o cuando usted cambia la configuración de la propiedad Text mediante código.

Sintaxis

Private Sub objeto_Change([índice As Integer])

La sintaxis del evento Change consta de las siguientes partes:

Parte Descripción

objeto Una expresión de objeto que da como resultado un objeto de la lista Aplicable a.

índice Un entero que identifica únicamente a un control si está en una matriz de controles.

Comentarios

Curso de Visual Basic II

230

Page 231: Programacion en Java

El procedimiento del evento Change puede sincronizar o coordinar la presentación de datos entre controles. Por ejemplo, puede utilizar un procedimiento de evento Change de una barra de desplazamiento para actualizar la configuración de la propiedad Value de la barra de desplazamiento de un control TextBox. O bien, puede utilizar un procedimiento de evento Change para mostrar datos y fórmulas en un área de trabajo y los resultados en otra área.

Los procedimientos de evento Change son también útiles para actualizar propiedades de controles del sistema de archivos (DirListBox, DriveListBox y FileListBox). Por ejemplo, puede actualizar la configuración de la propiedad Path para que un control DirListBox refleje un cambio en la configuración de la propiedad Drive de un control DriveListBox.

Nota Un procedimiento de evento Change puede algunas veces causar un evento en cascada. Esto ocurre cuando el procedimiento de evento Change del control altera el contenido del control, por ejemplo, estableciendo una propiedad en el código que determina el valor del control, como el valor de la propiedad Text para un control TextBox. Para impedir un evento en cascada:

• Si es posible, evite escribir un procedimiento de evento Change para un control que altere el contenido de ese control. Si escribe un procedimiento así, asegúrese de establecer un indicador que impida cambios posteriores mientras el cambio actual está en curso.

• Evite crear dos o más controles cuyos procedimientos de evento Change se vean afectados entre sí, por ejemplo, dos controles TextBox que se actualicen entre sí durante sus eventos Change.

• Evite utilizar una función o una instrucción MsgBox en este evento para los controles HScrollBar y VScrollBar.

Ejemplo del evento Change

Este ejemplo muestra la configuración numérica de la propiedad Value de una barra de desplazamiento horizontal en un control TextBox. Para probar este ejemplo, cree un formulario con un control TextBox y un control HScrollBar y después pegue el código en la sección Declaraciones de un formulario que contenga una barra de desplazamiento horizontal (control HScrollBar) y un control TextBox. Presione F5 y haga clic en la barra de desplazamiento horizontal.

Private Sub Form_Load ()

HScroll1.Min = 0 ' Establece Min.

HScroll1.Max = 1000 ' Establece Max.

HScroll1.LargeChange = 100 ' Establece LargeChange.

HScroll1.SmallChange = 1 ' Establece SmallChange.

End Sub

Private Sub HScroll1_Change ()

Text1.Text = HScroll1.Value

231

Page 232: Programacion en Java

End Sub

13.2-Click

Aplicable a:

Control CheckBox, Control ComboBox, Control CommandButton, Objeto Form, Control Frame, Control Image, Control Label, Control ListBox, Control Menu, Control OptionButton, Control PictureBox, Control TextBox.

Ocurre cuando el usuario presiona y suelta un botón del mouse (ratón) en un objeto. También puede ocurrir cuando se cambia el valor de un control.

Para un objeto Form, este evento ocurre cuando el usuario hace clic en un área en blanco o en un control desactivado. Para un control, este evento ocurre cuando el usuario:

• Hace clic en un control con el botón primario o secundario del mouse. Con un control CheckBox, CommandButton, ListBox o OptionButton, el evento Click sólo ocurre cuando el usuario hace clic con el botón primario del mouse.

• Selecciona un elemento de un control ComboBox o ListBox, ya sea presionando las teclas de dirección o haciendo clic con el botón del mouse.

• Presiona la BARRA ESPACIADORA cuando un control CommandButton, OptionButton o CheckBox tiene el enfoque.

• Presiona ENTRAR cuando un formulario tiene un control CommandButton con su propiedad Default establecida a True.

• Presiona ESC cuando un formulario tiene un botón Cancelar, un control CommandButton con su propiedad Cancel establecida a True.

• Presiona una tecla de acceso para un control. Por ejemplo, si el título de un control CommandButton es "&Ir", al presionar ALT+I se desencadena este evento.

También puede desencadenar el evento Click en el código si:

• Establece la propiedad Value de un control CommandButton a True.• Establece la propiedad Value de un control OptionButton a True.• Cambia el valor de la propiedad Value de un control CheckBox.

Sintaxis

Private Sub Form_Click( )

Private Sub objeto_Click([índice As Integer])

La sintaxis del evento Click consta de las siguientes partes:

Parte Descripción

objeto Una expresión de objeto que da como resultado un objeto de la lista Aplicable a.

índice Un entero que identifica únicamente a un control si está en una matriz de controles.

Comentarios

232

Page 233: Programacion en Java

Por lo general se adjunta un procedimiento de evento Click a un control CommandButton, un objeto Menu o un control PictureBox para realizar comandos y acciones similares a comandos. Para los demás controles aplicables, utilice este evento para desencadenar acciones como respuesta a un cambio en el control.

Puede utilizar la propiedad Value de un control para comprobar el estado del control desde el código. Hacer clic en un control genera los eventos MouseDown y MouseUp además del evento Click. El orden en que ocurren estos tres eventos varía de un control a otro. Por ejemplo, para los controles ListBox y CommandButton, los eventos ocurren en este orden: MouseDown, Click, MouseUp. Pero para los controles FileListBox, Label o PictureBox, los eventos ocurren en este otro orden: MouseDown, MouseUp y Click. Cuando está adjuntando procedimientos para estos eventos relacionados, asegúrese de que sus acciones no entran en conflicto. Si el orden de los eventos es importante en la aplicación, pruebe el control para determinar el orden de los mismos.

Nota Para distinguir entre los botones primario, secundario y central del mouse, utilice los eventos MouseDown y MouseUp.

Si hay código en el evento Click, nunca se activará el evento DlbClick ya que de los dos eventos, Click es el primero que se activa. Como resultado, el evento Click intercepta el clic del mouse, por lo que DblClick nunca se producirá.

Ejemplo del Evento Click

En este ejemplo, cada vez que se hace clic en un control PictureBox se mueve diagonalmente por un formulario. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario que contenga un control PictureBox en la esquina inferior izquierda del mismo, después presione F5 y haga clic en el control PictureBox.

Private Sub Picture1_Click ()

Picture1.Move Picture1.Left + 750, Picture1.Top - 550

End Sub

13.3- GotFocus

Aplicable a:

Control CheckBox, Control ComboBox, Control CommandButton, , Objeto Form Controles HScrollBar y VScrollBar, Control ListBox, Control OptionButton, Control PictureBox, Control TextBox.

Ocurre cuando un objeto recibe el enfoque, ya sea mediante una acción del usuario, como tabular o hacer clic en el objeto, o cambiando el enfoque en el código mediante el método SetFocus. Un formulario recibe el enfoque sólo cuando todos los controles visibles están desactivados.

Sintaxis

Private Sub Form_GotFocus( )

Private Sub objeto_GotFocus([índice As Integer])

La sintaxis del evento GotFocus consta de las siguientes partes:

233

Page 234: Programacion en Java

Parte Descripción

objeto Una expresión de objeto que da como resultado un objeto de la lista Aplicable a.

índice Un entero que identifica de manera única a un control si está en una matriz de controles.

Comentarios

Normalmente, el procedimiento de evento GotFocus se utiliza para especificar las acciones que ocurren cuando un control o un formulario recibe primero el enfoque. Por ejemplo, si adjunta un procedimiento de evento GotFocus a cada control de un formulario puede guiar al usuario mostrándole instrucciones breves o mensajes en la barra de estado. También puede proporcionar avisos visuales activando, desactivando o mostrando otros controles que dependan del control que tiene el enfoque.

Nota Un objeto puede recibir el enfoque sólo si sus propiedades Enabled y Visible están establecidas a True. Para personalizar el interfaz de teclado en Visual Basic para mover el enfoque, establezca el orden de tabulación o especifique teclas de acceso para controles de un formulario.

Ejemplo del evento GotFocus

Este ejemplo muestra un mensaje en la barra de estado cuando un botón de un grupo OptionButton obtiene el enfoque. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario que contenga dos controles OptionButton y un control Label. Establezca la propiedad Name de ambos controles OptionButton a OptionGroup y, después, presione F5 y haga clic en los controles OptionButton.

Private Sub Form_Load ()

Label1.AutoSize = True

End Sub

Private Sub OptionGroup_GotFocus (Index As Integer)

Select Case Index

Case 0

Label1.Caption = "La opción 1 tiene el enfoque."

Case 1

Label1.Caption = "La opción 2 tiene el enfoque."

End Select

End Sub

234

Page 235: Programacion en Java

Private Sub OptionGroup_LostFocus (Index As Integer)

Label1.Caption = ""

End Sub

13.4- KeyPress

Aplicable a:

Control CheckBox, Control ComboBox, Control CommandButton, Objeto Form Controles HScrollBar y VScrollBar, Control ListBox, Control OptionButton, Control PictureBox, Control TextBox.

Ocurre cuando el usuario presiona y suelta una tecla.

Sintaxis

Private Sub Form_KeyPress(keyascii As Integer)

Private Sub objeto_KeyPress([índice As Integer,]keyascii As Integer)

La sintaxis del evento KeyPress consta de las siguientes partes:

Parte Descripción

objeto Una expresión de objeto que da como resultado un objeto de la lista Aplicable a.

índice Un entero que identifica de manera única a un control si está en una matriz de controles.

keyascii Un entero que devuelve un código de tecla numérico ANSI estándar. keyascii se pasa por referencia; al cambiarlo se envía un carácter diferente al objeto. Cambiar keyascii a 0 cancela la pulsación de tecla, de forma que el objeto no recibe ningún carácter.

Comentarios

El objeto que tiene el enfoque recibe el evento. Un formulario puede recibir el evento sólo si no tiene controles visibles y activados. Un evento KeyPress puede implicar a cualquier carácter imprimible del teclado, a la tecla CTRL combinada con un carácter del alfabeto estándar o uno de los caracteres especiales, y la tecla ENTRAR o RETROCESO. Un procedimiento de evento KeyPress es útil para interceptar pulsaciones de teclas realizadas en un control TextBox o ComboBox. Esto le permite comprobar inmediatamente la validez de las pulsaciones o el formato de los caracteres a medida que se escriben. Cambiar el valor del argumento keyascii cambia el carácter mostrado.

KeyPress interpreta las mayúsculas y minúsculas de cada carácter como códigos de tecla distintos y, por tanto, como caracteres diferentes.

Nota El número ANSI para la combinación de teclado CTRL+@ es 0. Puesto que Visual Basic reconoce un valor keyascii de 0 como una cadena de longitud cero (""), evite utilizar CTRL+@ en sus aplicaciones.

235

Page 236: Programacion en Java

Ejemplo del evento KeyPress

Este ejemplo convierte a mayúsculas el texto escrito en un control TextBox. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario que contenga un control TextBox y, después, presione F5 y escriba algo en el control TextBox.

Private Sub Text1_KeyPress (KeyAscii As Integer)

Char = Chr(KeyAscii)

KeyAscii = Asc(UCase(Char))

End Sub

13.5- Load

Aplicable a:

Objeto Form.

Ocurre cuando se carga un formulario. Para un formulario de inicio, ocurre cuando una aplicación se inicia como resultado de una instrucción Load o como resultado de una referencia a una propiedad o control de un formulario descargado.

Sintaxis

Private Sub Form_Load( )

Private Sub MDIForm_Load( )

Comentarios

Normalmente utiliza un procedimiento de evento Load para incluir código de inicialización para un formulario; por ejemplo, código que especifica los valores predeterminados de los controles, indica el contenido que se va a cargar en controles ComboBox o ListBox e inicializa variables a nivel del formulario.

El evento Load ocurre tras el evento Initialize.

Nota Cuando cree procedimientos para eventos relacionados, como Activate, GotFocus, Paint y Resize, asegúrese de que sus acciones no entran en conflicto y no producen eventos recursivos.

Ejemplo del evento Load

Este ejemplo carga elementos en un control ComboBox cuando se carga un formulario. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario que contenga un control ComboBox y después presione F5.

Private Sub Form_Load ()

Combo1.AddItem "Mozart" ' Agrega elementos a la lista.

236

Page 237: Programacion en Java

Combo1.AddItem "Beethoven"

Combo1.AddItem "Rock 'n Roll"

Combo1.AddItem "Reggae"

Combo1.ListIndex = 2 ' Establece la selección predeterminada.

End Sub

13.6- LostFocus

Aplicable a

Control CheckBox, Control ComboBox, Control CommandButton, Objeto Form Controles HScrollBar y VScrollBar, Control ListBox, Control OptionButton, Control PictureBox, Control TextBox.

Ocurre cuando un objeto pierde el enfoque, ya sea por una acción del usuario, como tabular o hacer clic en otro objeto, o bien mediante un cambio del enfoque en el código con el método SetFocus.

Sintaxis

Private Sub Form_LostFocus( )

Private Sub objeto_LostFocus([índice As Integer])

La sintaxis del evento LostFocus consta de las siguientes partes:

Parte Descripción

objeto Una expresión de objeto que da como resultado un objeto de la lista Aplicable a.

índice Un entero que identifica de manera única a un control si está en una matriz de controles.

Comentarios

Un procedimiento de evento LostFocus resulta especialmente útil para comprobar y validar actualizaciones. Utilizar LostFocus puede hacer que la validación tenga lugar conforme el usuario mueve el enfoque del control. Otro uso para este tipo de procedimiento de evento es activar, desactivar, ocultar y mostrar otros objetos, como en un procedimiento de evento GotFocus. También puede invertir o cambiar condiciones que estableció en el procedimiento de evento GotFocus del objeto.

Ejemplo del evento LostFocus

Este ejemplo cambia el color de un control TextBox cuando recibe o pierde el enfoque (se selecciona con el mouse o la tecla TAB) y muestra el texto apropiado en el control Label. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario que contenga

237

Page 238: Programacion en Java

dos controles TextBox y un control Label y, después, presione F5 y mueva el enfoque entre Text1 y Text2.

Private Sub Text1_GotFocus ()

' Muestra el enfoque en rojo.

Text1.BackColor = RGB(255, 0, 0)

Label1.Caption = "Text1 tiene el enfoque."

End Sub

Private Sub Text1_LostFocus ()

' Muestra la pérdida del enfoque en azul.

Text1.BackColor = RGB(0, 0, 255)

Label1.Caption = "Text1 no tiene el enfoque."

End Sub

13.7- MouseMove

Aplicable a:

Control CheckBox, Control CommandButton, Control Data, Objeto Form, Control Frame, Control Image, Control Label, Control ListBox, Control OptionButton, Control PictureBox, Control TextBox.

Ocurre cuando el usuario mueve el mouse.

Sintaxis

Private Sub Form_MouseMove(botón As Integer, mayús As Integer, x As Single, y As Single)

Private Sub MDIForm_MouseMove(botón As Integer, mayús As Integer, x As Single, y As Single)

Private Sub objeto_MouseMove([índice As Integer,] botón As Integer, mayús As Integer, x As Single, y As Single)

La sintaxis del evento MouseMove consta de las siguientes partes:

Parte Descripción

objeto Una expresión de objeto que da como resultado un objeto de la lista Aplicable a.

índice Un entero que identifica de manera única a un control si está en una matriz de controles.

238

Page 239: Programacion en Java

botón Un entero que corresponde al estado de los botones del mouse en el cual un bit se establece si el botón está presionado. El argumento botón es un campo de bit con los bits correspondientes al botón primario (bit 0), al botón secundario (bit 1) y al botón central (bit 2). Estos bits corresponden a los valores 1, 2 y 4, respectivamente. Indica el estado completo de los botones del mouse; alguno, todos o ninguno de estos tres bits puede estar establecido, lo que indica que algunos, todos o ninguno de los botones está presionado.

mayús Un entero que corresponde al estado de las teclas MAYÚS, CTRL y ALT. Un bit está establecido si la tecla está presionada. El argumento mayús es un campo de bits con los bits menos significativos correspondientes a la tecla MAYÚS (bit 0), CTRL (bit 1) y ALT (bit 2 ). Estos bits corresponden a los valores 1, 2 y 4, respectivamente. El argumento mayús indica el estado de estas teclas. Alguno, todos o ninguno de los bits puede estar establecido, lo que indica que alguna, todas o ninguna de las teclas está presionada. Por ejemplo, si se presionaron las teclas CTRL y ALT, el valor de shift sería 6.

x, y Un número que especifica la ubicación actual del puntero del mouse. Los valores x e y siempre se expresan en términos del sistema de coordenadas establecido por las propiedades ScaleHeight, ScaleWidth, ScaleLeft y ScaleTop del objeto.

Comentarios

El evento MouseMove se genera continuamente a medida que el puntero del mouse se mueve por los objetos. A menos que otro objeto haya capturado el mouse, un objeto reconoce un evento MouseMove siempre que la posición del mouse esté dentro de sus bordes.

Ejemplo del evento MouseMove

Este ejemplo muestra una aplicación de dibujo simple. El procedimiento de evento MouseDown funciona con un procedimiento MouseMove relacionado para activar el dibujo cuando está presionado cualquier botón del mouse. El procedimiento de evento MouseUp desactiva el dibujo. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario, después presione F5, haga clic en el formulario y mueva el mouse mientras está presionado el botón del mismo.

Dim PaintNow As Boolean ' Declara una variable.

Private Sub Form_MouseDown (Button As Integer, Shift As Integer, X As Single, Y As Single)

PaintNow = True ' Activa el dibujo.

End Sub

Private Sub Form_MouseUp (Button As Integer, X As Single, Y As Single)

PaintNow = False ' Desactiva el dibujo.

End Sub

Private Sub Form_MouseMove (Button As Integer, Shift As Integer, X As Single, Y As Single)

If PaintNow Then

239

Page 240: Programacion en Java

PSet (X, Y) ' Dibuja un punto.

End If

End Sub

Private Sub Form_Load ()

DrawWidth = 10 ' Utiliza un pincel más ancho.

ForeColor = RGB(0, 0, 255) ' Establece el color de dibujo.

End Sub

13.8- Timer

Aplicable a:

Control Timer

Ocurre cuando ha transcurrido un intervalo preestablecido para un control Timer. La frecuencia del intervalo se almacena en la propiedad Interval del control, que especifica el tiempo en milisegundos.

Sintaxis

Private Sub objeto_Timer([índice As Integer])

La sintaxis del evento Timer consta de las siguientes partes:

Parte Descripción

objeto Una expresión de objeto que da como resultado un objeto de la lista Aplicable a.

índice Un entero que identifica de manera única a un control si está en una matriz de controles.

Comentarios

Utilice este procedimiento de evento para indicar a Visual Basic qué hacer cada vez que se agote el intervalo de tiempo de un control Timer. Cuando esté trabajando con el evento Timer:

• La propiedad Interval especifica el intervalo entre los eventos Timer, en milisegundos.• Siempre que la propiedad Enabled del control Timer esté establecida a True y la propiedad

Interval sea mayor que 0, el evento Timer espera durante el periodo especificado en la propiedad Interval.

Ejemplo del evento Timer

240

Page 241: Programacion en Java

Este ejemplo muestra un reloj digital. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario que contenga un control Label y un control Timer y, después, presione F5.

Private Sub Form_Load ()

Timer1.Interval = 1000 ' Establece el intervalo de Timer.

End Sub

Private Sub Timer1_Timer ()

Label1.Caption = Time ' Actualiza la presentación de la hora.

End Sub

Este ejemplo mueve un control PictureBox por un formulario. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario que contenga un control Timer y un control PictureBox y, después, presione F5. Si desea obtener un efecto visual mejor puede asignar un mapa de bits al control PictureBox mediante la propiedad Picture.

Dim DeltaX, DeltaY As Integer ' Declara variables.

Private Sub Timer1_Timer ()

Picture1.Move Picture1.Left + DeltaX, Picture1.Top + DeltaY

If Picture1.Left < ScaleLeft Then DeltaX = 100

If Picture1.Left + Picture1.Width > ScaleWidth + ScaleLeft Then

DeltaX = -100

End If

If Picture1.Top < ScaleTop Then DeltaY = 100

If Picture1.Top + Picture1.Height > ScaleHeight + ScaleTop Then

DeltaY = -100

End If

End Sub

Private Sub Form_Load ()

Timer1.Interval = 1000 ' Establece el intervalo.

241

Page 242: Programacion en Java

DeltaX = 100 ' Inicializa variables.

DeltaY = 100

End Sub

13.9- Unload

Aplicable a:

Objeto Form.

Ocurre cuando un formulario está a punto de quitarse de la pantalla. Cuando ese formulario se vuelve a cargar, el contenido de todos sus controles se reinicializa. Este evento se desencadena porque un usuario cierra el formulario mediante el comando Cerrar del menú Control o una instrucción Unload.

Sintaxis

Private Sub objeto_Unload(cancelar As Integer)

La sintaxis del evento Unload consta de las siguientes partes:

Parte Descripción

objeto Una expresión de objeto que da como resultado un objeto de la lista Aplicable a.

cancelar Un entero que determina si el formulario se quita de la pantalla. Si cancelar es 0, el formulario se quita. Establecer cancelar a cualquier valor distinto de cero impide que el formulario se quite.

Comentarios

Establecer cancelar a un valor distinto de cero impide que el formulario se quite, pero no detiene los demás eventos, como la salida del entorno operativo Microsoft Windows. Utilice el evento QueryUnload para detener la salida de Windows.

Utilice un procedimiento de evento Unload para comprobar si el formulario se debe descargar o para especificar acciones que desea que tengan lugar cuando se descargue el formulario. También puede incluir cualquier código de validación a nivel del formulario que pueda necesitar para cerrar el formulario o guardar los datos en un archivo.

El evento QueryUnload ocurre antes que el evento Unload. El evento Unload ocurre antes que el evento Terminate.

El evento Unload puede estar causado por la utilización de la instrucción Unload o porque el usuario elija el comando Cerrar del menú Control del formulario, salga de la aplicación con el botón Finalizar tarea de la Lista de tareas, o salga del entorno operativo Microsoft Windows mientras la aplicación se está ejecutando.

Ejemplo del evento Unload

242

Page 243: Programacion en Java

Este ejemplo muestra un procedimiento simple para cerrar un formulario mientras se avisa al usuario con varios cuadros de mensajes. En una aplicación real, puede agregar llamadas a procedimientos Sub de propósito general que emulen el proceso de los comandos Salir, Guardar y Guardar como del menú Archivo de Visual Basic. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario y, después, presione F5. Una vez que se muestre el formulario, presione ALT+F4 para cerrar el formulario.

Private Sub Form_Unload (Cancel As Integer)

Dim Msg, Response ' Declara variables.

Msg = "¿Desea guardar los datos antes de cerrar?"

Response = MsgBox(Msg, vbQuestion + vbYesNoCancel, "Diálogo Cerrar")

Select Case Response

Case vbCancel ' No se permite cerrar.

Cancel = -1

Msg = "Se ha cancelado el comando."

Case vbYes

' Introduzca código para guardar los datos aquí.

Msg = "Datos guardados."'

Case vbNo

Msg = "Datos no guardados."

End Select

MsgBox Msg, vbOKOnly, "Confirmación" ' Mostrar mensaje.

End Sub

13.10- QueryUnload

Aplicable a:

Objeto Form y Colección Forms, Objeto MDIForm

Ocurre antes de que se cierre un formulario o una aplicación.

Sintaxis

243

Page 244: Programacion en Java

Private Sub Form_QueryUnload(cancelar As Integer, modo_descarga As Integer)

Private Sub MDIForm_QueryUnload(cancelar As Integer, modo_descarga As Integer)

La sintaxis del evento QueryUnload consta de las siguientes partes:

Parte Descripción

cancelar Un entero. Establecer este argumento a cualquier valor distinto de 0 detiene el evento QueryUnload en todos los formularios cargados y detiene el cierre del formulario y de la aplicación.

modo_descarga Un valor o una constante que indica la causa del evento QueryUnload, tal y como se describe en Valores que se pueden obtener.

Valores que se pueden obtener

El argumento modo_descarga devuelve los siguientes valores:

Constante Valor Descripción

vbFormControlMenu 0 El usuario eligió el comando Cerrar del menú Control del formulario.

vbFormCode 1 Se invocó la instrucción Unload desde el código.

VbAppWindows 2 La sesión actual del entorno operativo Microsoft Windows está inalizando.

vbAppTaskManager 3 El Administrador de tareas de Microsoft Windows está cerrando la

aplicación.

vbFormMDIForm 4 Un formulario MDI secundario se está cerrando porque el formulario MDI

también se está cerrando.

Comentarios

Normalmente este evento se utiliza para asegurarse de que no hay tareas sin finalizar en los formularios incluidos en una aplicación antes de que esa aplicación se cierre. Por ejemplo, si un usuario no ha guardado todavía algunos datos nuevos de cualquier formulario, su aplicación puede pedir al usuario que los guarde.

Cuando una aplicación se cierra, puede utilizar los procedimientos de evento QueryUnload o Unload para establecer la propiedad Cancel a True, deteniendo el proceso de cierre. Sin embargo, el evento QueryUnload ocurre en todos los formularios antes de que se descargue ninguno de ellos y el evento Unload ocurre conforme se descarga cada formulario.

244

Page 245: Programacion en Java

Ejemplo del evento QueryUnload

En este ejemplo, al cerrar un formulario se consulta al operador si realmente quiere salir o no. También se chequea si está saliendo del formulario o de toda la aplicación.

' Pegar en la sección Declaraciones de Form1.

Private Sub Form_QueryUnload (Cancel As Integer, UnloadMode As Integer)

Dim Msg ' Declara la variable.

If UnloadMode > 0 Then

' Si sale de la aplicación.

Msg = "¿Realmente desea salir de la aplicación?"

Else

' Si sólo se cierra el formulario.

Msg = "¿Realmente desea cerrar el formulario?"

End If

' Si el usuario hace clic en el botón No, se detiene QueryUnload.

If MsgBox(Msg, vbQuestion + vbYesNo, Me.Caption) = vbNo Then Cancel = True

End Sub

13.11- Validate

Aplicable a:

Control Data

Se produce antes de que otro registro se convierta en el registro actual, antes del método Update (excepto cuando se guardan los datos con el método UpdateRecord) y antes de una operación Delete, Unload o Close.

Sintaxis

Private Sub objeto_Validate ([índice As Integer,] acción As Integer, guardar As Integer)

La sintaxis del evento Validate consta de las siguientes partes:

Parte Descripción

245

Page 246: Programacion en Java

objeto Una expresión de objeto cuyo resultado es un objeto de la lista Aplicable a

índice Identifica el control si se encuentra en una matriz de controles

acción Un entero que indica la operación que provoca el evento, como se describe en Valores

guardar Una expresión booleana que especifica si los datos enlazados han cambiado, como se describe en Valores

Valores

Los valores de acción son:

Constante Valor Descripción

vbDataActionCancel 0 Cancela la operación al salir de Sub

vbDataActionMoveFirst 1 Método MoveFirst

vbDataActionMovePrevious 2 Método MovePrevious

vbDataActionMoveNext 3 Método MoveNext

vbDataActionMoveLast 4 Método MoveLast

vbDataActionAddNew 5 Método AddNew

vbDataActionUpdate 6 Operación Update (no UpdateRecord)

vbDataActionDelete 7 Método Delete

vbDataActionFind 8 Método Find

vbDataActionBookmark 9 Se ha establecido la propiedad Bookmark

vbDataActionClose 10 El método Close

vbDataActionUnload 11 Se está descargando el formulario

Los valores de guardar son:

Valor Descripción

True Los datos enlazados han cambiado

False Los datos enlazados no han cambiado

Comentarios

El argumento guardar indica inicialmente si los datos enlazados han cambiado. Este argumento puede ser False si los datos del búfer de copia han cambiado. Si guardar es True cuando este

246

Page 247: Programacion en Java

evento termina, se invocan los métodos Edit y UpdateRecord. El método UpdateRecord sólo guarda los datos de controles enlazados o del búfer de copia en los que la propiedad DataChanged sea True.

Este evento se produce incluso aunque no se hayan modificado los datos de los controles enlazados y aunque no existan controles enlazados. Puede utilizar este evento para cambiar valores y actualizar datos. También puede decidir guardar los datos o detener cualquier acción que esté provocando el evento y sustituirla por otra acción diferente.

Puede cambiar el argumento acción para cambiar una acción por otra. Puede cambiar los diversos métodos Move y el método AddNew, que se pueden intercambiar libremente (cualquier Move en AddNew, cualquier Move en cualquier otro Move o AddNew en cualquier Move). Cuando utilice AddNew, puede utilizar MoveNext y después ejecutar otro AddNew para examinar la propiedad EditMode y determinar si hay una operación Edit o AddNew en curso. El intento de sustituir AddNew o una acción Move en cualquier otra acción se pasa por alto o genera un error interceptable. Si se establece acción a 0, se puede detener cualquier acción.

Dentro del código de este evento puede comprobar los datos de cada control enlazado en el que DataChanged sea True. Después puede establecer DataChanged a False para evitar guardar dichos datos en la base de datos.

Durante este evento no puede utilizar ningún método (como MoveNext) en el objeto Recordset subyacente.

Ejemplo de la propiedad DataChanged y del evento Validate

Este ejemplo ilustra una validación de datos sencilla. En la tabla Authors de la base de datos Biblio.mdb hay dos campos: Au_ID y Author. Como el valor de Au_ID se utiliza para identificar de forma única al autor, este valor no se debe cambiar. El ejemplo no permite que se modifique el campo Au_ID, que está enlazado a Text1.

Private Sub Data1_Validate (Action As Integer, Save As Integer)

If Text1.DataChanged Then ' Comprueba si los datos han cambiado.

MsgBox "No puede cambiar el número de Id."

Text1.DataChanged = False ' No guarda los datos modificados.

End If

...

End Sub

14- Métodos más importantes de los controles estándares.

14.1- AddItem

Aplicable a:

247

Page 248: Programacion en Java

ControlComboBox, ControlListBox

Agrega un elemento a un control ListBox o ComboBox.

Sintaxis

objeto.AddItem elemento, índice

La sintaxis del método AddItem consta de las siguientes partes:

Parte Descripción

objeto Requerido. Una expresión de objeto cuyo resultado es un objeto de la lista Aplicable a.

Elemento Requerido. expresión de cadena que especifica el elemento que se va a agregar al objeto.

índice Opcional. Entero que especifica la posición dentro del objeto donde se insertan el elemento o la fila nuevos. Para el primer elemento de un control ListBox o ComboBox, índice es 0.

Comentarios

Si se especifica un valor válido para índice, elemento se sitúa en dicha posición dentro del objeto. Si se omite índice, elemento se agrega en la posición que le corresponda dentro del orden apropiado (si la propiedad Sorted es True) o al final de la lista (si Sorted es False).

Los controles ListBox o ComboBox que están enlazados a un control Data no aceptan el método AddItem.

Ejemplo del método AddItem

Este ejemplo utiliza el método AddItem para agregar 100 elementos a un cuadro de lista. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario con un control ListBox llamado List1, y después presione F5 y haga clic en el formulario.

Private Sub Form_Click ()

Dim Entry, I, Msg ' Declara variables.

Msg = "Haga clic en Aceptar para agregar 100 elementos a su cuadro de lista."

MsgBox Msg ' Muestra el mensaje.

For I = 1 To 100 ' Cuenta de 1 a 100.

Entry = "Entrada " & I ' Crea la entrada.

List1.AddItem Entry ' Agrega la entrada.

Next I

248

Page 249: Programacion en Java

Msg = "Haga clic en Aceptar para quitar una de cada dos entradas."

MsgBox Msg ' Muestra el mensaje.

For I = 1 To 50 ' Determina cómo quitar

List1.RemoveItem I ' cada elemento

Next I

Msg = "Haga clic en Aceptar para quitar todos los elementos del cuadro de lista."

MsgBox Msg ' Muestra el mensaje.

List1.Clear ' Limpia el cuadro de lista.

End Sub

14.2- AddNew (Objeto Recordset)

Aplicable a:

Objeto Recordset.

Crea un nuevo registro para un objeto Recordset de tipo Table o Dynaset.

Sintaxis

recordset.AddNew

El marcador de posición del recordset es una variable de objeto que representa un objeto Recordset que se puede actualizar al que puede agregar un registro nuevo.

Comentarios

Utilice el método AddNew para crear y agregar un nuevo registro en el objeto Recordset llamado por el recordset. Este método establece los campos a los valores predeterminados y si no se especifican valores predeterminados, establece los campos a Null (los valores predeterminados especificados pare el Recordset tipo Table).

Después de modificar el nuevo registro, utilice el método Update para guardar los cambios y agregar el registro al Recordset. No se producirán cambios en la base de datos hasta que no se utilice el método Update.

Precaución Si ejecuta un AddNew y a continuación realiza una operación que desplace otro registro sin usar Update, los cambios se perderán sin previo aviso. Además, si cierra el Recordset o finaliza el procedimiento que declara el Recordset o su objeto Database, el nuevo registro y los cambios realizados se descartarán sin previo aviso.

249

Page 250: Programacion en Java

Si no se ha desplazado hasta el último registro de su Recordset, los registros agregados a las tablas subyacentes pueden incluirse, si se colocan más allá del registro activo. Sin embargo, si agrega un registro a un Recordset, el registro será visible en el Recordset y se incluirá en la tabla subyacente donde estará visible para todos los nuevos objetos Recordset.

La posición del nuevo registro depende del tipo de Recordset:

• En un objeto Recordset tipo Dynaset, los registros se insertan al final del conjunto del Recordset, independientemente de las reglas de clasificación u orden que estuvieran en vigor cuando se abrió el Recordset.

• En un objeto Recordset tipo Table en el que su propiedad Index se haya establecido, los registros se insertan en el lugar adecuado dentro del orden definido. Si no se ha establecido la propiedad Index, los nuevos registros se insertarán al final del Recordset.

Ejemplo de método AddNew

En este ejemplo se agrega un regisro nuevo a la tabla Agenda de la base de datos Clientes, tomando los datos desde un formulario que contiene 3 cajas de texto (txtCodigo, txtNombre y txtDireccion) y un botón para agregar los datos (cmdAgregar)

Private Sub cmdAgregar_Click ()

Dim wsp as WorkSpace ‘Dimensiono las variables

Dim Base as Database

Dim Agenda as Recordset

Set wsp = DbEngine.Workspaces(0) ‘Seteo el espacio de trabajo

Set Base = wsp.OpenDatabase (“Clientes.mdb”) ‘Abro la base de Datos

Set Agenda = BasedeDatos.OpenRecordset(“SELECT * FROM Agenda”) ‘Abro el Recordset

Agenda.AddNew ‘Agrego un registro en blanco

Agenda!Codigo = txtCodigo.Text ‘Asigno los valores de las cajas de texto a los campos

Agenda!Direccion = txtDireccion.Text

Agenda.Nombre = txtNombre.Text

Agenda.Update

Agenda.Close ‘Cierro el Recordset

Base.Close ‘Cierro la base de Datos

Wsp.Close ‘Cierro el espacio de trabajo

250

Page 251: Programacion en Java

End Sub

14.3- CancelUpdate (Objeto Recordset)

Aplicanble a:

Objeto Recordset.

Cancela todas las actualizaciones pendientes del objeto Recordset.

Sintaxis

recordset.CancelUpdate tipo

La sintaxis del método CancelUpdate consta de las siguientes partes.

Parte Descripción

recordset Una variable de objeto que representa el objeto Recordset en el que se cancelan las actualizaciones pendientes.

Tipo Opcional. Una constante que indica el tipo de actualización, como se especifica en Valores (sólo espacios de trabajo ODBCDirect).

Comentarios

El método CancelUpdate cancela todas las actualizaciones pendientes a causa de una operación Edit o AddNew. Por ejemplo, si un usuario llama al método Edit o AddNew sin haber llamado anteriormente al método Update, CancelUpdate cancelará todos los cambios efectuados después de llamar a Edit o AddNew.

Ejemplo del método CancelUpdate

Al mismo ejemplo del Evento AddNew, le agregamos la opción de confirmar o volver para atrás la actualización al operador, según la respuesta a una caja de mensajes.

Private Sub cmdAgregar_Click ()

Dim wsp as WorkSpace ‘Dimensiono las variables

Dim Base as Database

Dim Agenda as Recordset

Set wsp = DbEngine.Workspaces(0) ‘Seteo el espacio de trabajo

Set Base = wsp.OpenDatabase (“Clientes.mdb”) ‘Abro la base de Datos

Set Agenda = BasedeDatos.OpenRecordset(“SELECT * FROM Agenda”) ‘Abro el Recordset

251

Page 252: Programacion en Java

Agenda.AddNew ‘Agrego un registro en blanco

Agenda!Codigo = txtCodigo.Text ‘Asigno los valores de las cajas de texto a los campos

Agenda!Direccion = txtDireccion.Text

Agenda.Nombre = txtNombre.Text

If MsgBox ("Agrega el nuevo registro”, vbYesNo) = vbYes then

Agenda.Update

Else

Agenda.CancelUpdate

End If

Agenda.Close ‘Cierro el Recordset

Base.Close ‘Cierro la base de Datos

Wsp.Close ‘Cierro el espacio de trabajo

End Sub

14.4- Clear (Clipboard, Combo Box, List Box)

Aplicable a:

Objeto Control ComboBox, Control ListBox-

Borra el contenido de los controles ListBox o ComboBox.

Sintaxis

objeto.Clear

El marcador de posición objeto representa una expresión de objeto cuyo resultado es un objeto de la lista Aplicable a.

Comentarios

Los controles ListBox o ComboBox que estén enlazados a un control Data no aceptan el método Clear.

Ejemplo del método Clear

252

Page 253: Programacion en Java

Este ejemplo utiliza el método Clear para borrar todos los elementos de un cuadro de lista. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario con un control ListBox llamado List1, y después presione F5 y haga clic en el formulario.

Private Sub Form_Click ()

Dim Entry, I, Msg ' Declara variables.

Msg = "Haga clic en Aceptar para agregar 100 elementos a su cuadro de lista."

MsgBox Msg ' Muestra el mensaje.

For I = 1 To 100 ' Cuenta de 1 a 100.

Entry = "Entrada " & I ' Crea la entrada.

List1.AddItem Entry ' Agrega la entrada.

Next I

Msg = "Haga clic en Aceptar para quitar cualquier otra entrada."

MsgBox Msg ' Muestra el mensaje.

For I = 1 To 50 ' Determina cómo quitar

List1.RemoveItem I ' uno de cada dos

Next I ' elementos.

Msg = "Haga clic en Aceptar para quitar todos los elementos del cuadro de lista."

MsgBox Msg ' Muestra el mensaje.

List1.Clear ' Limpia el cuadro de lista.

End Sub

14.5- Close (Objetos Database, Recordset o Workspace)

Aplicable a:

Objeto Database, Objeto Recordset, Objeto Workspace.

Cierra un objeto DAO (Data Access Object).

253

Page 254: Programacion en Java

Sintaxis

objeto.Close

El marcador de posición objeto es una variable de objeto que representa un objeto Database, Recordset o Workspace abierto.

Comentarios

Si el objeto Database, Recordset o Workspace llamado por objeto está cerrado cuando utiliza Close se produce un error en tiempo de ejecución.

Precaución Si sale de un procedimiento que declara objetos Database o Recordset y la base de datos esté cerrada, los cambios no guardados se perderán, todas las transacciones pendientes se anularán y se anularán todas las modificaciones pendientes de los datos.

Si intenta cerrar un objeto Connection o Database mientras hay algún objeto Recordset abierto, estos objetos Recordset se cerrarán y las actualizaciones o modificaciones pendientes quedarán anuladas. Si intenta cerrar un objeto Workspace mientras hay algún objeto Database abierto, los objetos Database se cerrarán, el cual cerrará sus objetos Recordset.

La utilización del método Close en un objeto Recordset original o duplicado no afecta al otro objeto Recordset.

Una alternativa al método Close es establecer el valor de una variable de objeto a Nothing (Set dbsTemp = Nothing).

Ejemplo de método Close

Son válidos los ejemplos de los métodos AddNew y CancelUpdate. (donde se cierran objetos Workspace, Database y Recordset),

14.6- Cls

Aplicable a:

Objeto Form, Control PictureBox.

Borra los gráficos y el texto generados en tiempo de ejecución de los controles Form o PictureBox.

Sintaxis

objeto.Cls

El marcador de posición objeto representa una expresión de objeto cuyo resultado es un objeto de la lista Aplicable a. Si se omite objeto, se supone que el objeto es el control Form que tenga el enfoque.

Comentarios

254

Page 255: Programacion en Java

Cls borra el texto y los gráficos generados en tiempo de ejecución por instrucciones gráficas y de impresión. Los mapas de bits de fondo definidos mediante la propiedad Picture y los controles colocados en un Form en tiempo de diseño no se ven afectados por Cls.

Después de llamar a Cls, las propiedades CurrentX y CurrentY del objeto se restablecen a 0.

Ejemplo del método Cls

Este ejemplo utiliza el método Cls para eliminar la información impresa de un formulario. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario, y después presione F5 y haga clic en el formulario.

Private Sub Form_Click ()

Dim Msg ' Declara variable.

ForeColor = QBColor(15) ' Establece el color de primer plano a blanco.

BackColor = QBColor(1) ' Establece el color de fondo a azul.

Msg = "Esta información se imprime en el fondo del formulario."

Print Msg ' Imprime el mensaje en el formulario.

Msg = "Haga clic en Aceptar para borrar la información y el patrón de fondo "

Msg = Msg & "mostrado en el formulario."

MsgBox Msg ' Muestra el mensaje.

Cls ' Borra el fondo del formulario.

End Sub

14.7- CompactDatabase (Objeto DBEngine)

Aplicable a:

Objeto DBEngine

Copia, compacta y ofrece la posibilidad de cambiar la versión, la secuencia de ordenación y la codificación. (sólo espacio de trabajo Microsoft Jet).

Sintaxis

DBEngine.CompactDatabase antiguabasededatos, nuevabasededatos, escenario, opciones, contraseña

255

Page 256: Programacion en Java

La sintaxis del método CompactDatabase tiene los siguientes argumentos:

-Antiguabasededatos: Una String que identifica una base de datos existente y cerrada. Puede ser una ruta completa y un nombre de archivo, como "C:\db1.mdb". Si el nombre de archivo tiene una extensión, deberá especificarla. Si su red lo admite, también puede especificar una ruta de red, como "\\server1\share1\dir1\db1.mdb".

-nuevabasededatos: Un tipo de datos String que es la ruta completa de la base de datos compactada que va a crear. También puede especificar una ruta de acceso de red al igual que con antiguabasededatos. No puede usar el argumento nuevabasededatos para especificar el mismo archivo de base de datos que antiguabasededatos.

-Escenario: Opcional. Un tipo de datos Variant que es una expresión de cadena que se utiliza para especificar la secuencia de ordenación para crear nuevabasededatos, como se especifica en Opciones. Si omite este argumento, el escenario de la nuevabasededatos será el mismo que el de la antiguabasededatos.

También puede crear una contraseña para nuevabasededatos concatenando la cadena de la contraseña (que comienza con ";pwd=") con una constante del argumento escenario, como este:

dbLangSpanish & ";pwd=NuevaContraseña"

Si desea utilizar el mismo escenario como antiguabasededatos (el valor predeterminado), pero especificar una contraseña nueva, simplemente escriba una contraseña en escenario:

";pwd=NuevaContraseña"

opciones Opcional. Un valor entero que indica una o más opciones, según se especifica en Opciones. Puede combinar opciones sumando las correspondientes constantes.

-Contraseña: Opcional. Un tipo de datos Variant que es una expresión de cadena que contiene una contraseña, si la base de datos está protegida con contraseña. La cadena ";pwd=" debe preceder a la propia contraseña. Si incluye una valor de contraseña en escenario, este valor se ignora.

Puede utilizar una de las siguientes constantes en el argumento opciones para especificar si desea o no codificar la base de datos mientras se compacta.

Constante Descripción

dbEncrypt Codifica la base de datos durante la compactación.

DbDecrypt Descodifica la base de datos durante la compactación.

Si omite una constante de codificación o si incluye a la vez dbDecrypt y dbEncrypt, nuevabasededatos tendrá la misma codificación que antiguabasededatos.

Puede usar una de las siguientes constantes en el argumento opciones para especificar la versión del formato de los datos para la base de datos compactada. Esta constante afecta sólo a la versión del formato de datos de nuevabasededatos y no afecta a la versión de ninguno de los objetos definidos por Microsoft Access, como formularios e informes.

Constante Descripción

256

Page 257: Programacion en Java

dbVersion10 Crea una base de datos que utiliza el motor de base de datos Microsoft Jet versión 1.0

durante la compactación.

DbVersion11 Crea una base de datos que utiliza el motor de base de datos Microsoft Jet versión 1.1

durante la compactación.

DbVersion20 Crea una base de datos que utiliza el motor de base de datos Microsoft Jet versión 2.0

durante la compactación.

DbVersion30 Crea una base de datos que utiliza el motor de base de datos Microsoft Jet versión 3.0 (compatible con la versión 3.5) durante la compactación.

Sólo puede especificar una constante de versión. Si omite una constante de versión, nuevabasededatos tendrá la misma versión que antiguabasededatos. Sólo puede compactar nuevabasededatos a una versión igual o posterior a la de antiguabasededatos.

Comentarios

Al cambiar datos de una base de datos, el archivo de base de datos puede fragmentarse y utilizar más espacio en disco del necesario. Regularmente, puede usar el método CompactDatabase en la base de datos para desfragmentar el archivo de base de datos. La base de datos compactada suele ser más pequeña y ejecutarse con más rapidez. También puede cambiar la secuencia de ordenación, la codificación o la versión del formato de datos, mientras copia y compacta la base de datos.

Tiene que cerrar antiguabasededatos antes de compactarla. En un entorno multiusuario, los demás usuarios no pueden tener abierta antiguabasededatos mientras usted la compacta. Si antiguabasededatos no está cerrada o no se encuentra disponible para su uso exclusivo, se producirá un error.

Puesto que CompactDatabase crea una copia de la base de datos, deberá disponer de espacio suficiente en disco para la base de datos original y la duplicada. La operación de compactación fracasará si no hay suficiente espacio disponible en disco. La base de datos duplicada nuevabasededatos no tiene por qué estar en el mismo disco que antiguabasededatos. Después de compactar una base de datos, puede eliminar el archivo antiguabasededatos y cambiar el nombre del archivo compactado nuevabasededatos por el nombre del archivo original.

El método CompactDatabase copia todos los datos y valores de permisos de seguridad de la base de datos especificada en antiguabasededatos a la base de datos especificada en nuevabasededatos.

Si utiliza el método CompactDatabase para convertir una base de datos versión 1.x a una versión 2.5 o 3.x, solamente las aplicaciones que utilizan las versiones de Microsoft Jet 2.5 o 3.x pueden abrir la base de datos convertida.

Precaución Debido a que el método CompactDatabase no convertirá objetos Microsoft Access, no es recomendable utilizar CompactDatabase para convertir una base de datos que contenga dichos

257

Page 258: Programacion en Java

objetos. Para convertir una base de datos que contenga objetos Microsoft Access, en el menú Herramientas, elija Utilidades de la base de datos y después haga clic en Convertir base de datos.

Ejemplo de método CompactDatabase

Este ejemplo utiliza el método CompactDatabase compactar la base de Datos Clientes.mdb.

Sub CompactDatabaseX()

DBEngine.CompactDatabase "Clientes.mdb", “Compact.mdb"

End Sub

14.8- Delete (Objeto Recordset)

Aplicable a:

Objeto Recordset.

• Objetos Recordset: elimina el registro activo de un objeto Recordset de tipo Dynaset o Table. Para espacios de trabajo ODBCDirect, el tipo de controlador determina si los objetos Recordset se pueden actualizar y, por tanto, admiten el método Delete.

Sintaxis

recordset.Delete

La sintaxis del método Delete utiliza los siguientes argumentos.

Argumentos Descripción

recordset Una variable de objeto que identifica un objeto Recordset de tipo Dynaset o Table abierto, que contiene el registro que desea eliminar.

Comentarios

Puede utilizar el método Delete para eliminar un registro activo de un objeto Recordset.

Recordsets

Un objeto Recordset debe contener un registro activo antes de que utilice el método Delete; en caso contrario se produce un error en tiempo de ejecución.

En objetos Recordset, Delete elimina el registro activo y lo hace inaccesible. Aunque no pueda modificarlo o utilizarlo, el registro eliminado permanecerá activo. Sin embargo, una vez que se desplace a otro registro no podrá volver a convertir en activo el registro eliminado. Las referencias subsiguientes a un registro eliminado en un Recordset no son válidas y producen un error.

Si la tabla base es la tabla principal en una relación de eliminación de cascada, al eliminar el registro activo también se eliminarán uno o más registros de una tabla externa.

258

Page 259: Programacion en Java

Nota Para agregar, modificar o eliminar un registro, debe tener un índice único en el registro en el origen de datos de base. Si no es así, se producirá un error "Permiso denegado" en la llamada al método AddNew, Delete o Edit en un espacio de trabajo Microsoft Jet.

Ejemplo de método Delete

Se necesitan borrar todos los registros de la tabla Agenda (dentro de la base de datos Clientes) cuyos códigos de Clientes sean menores a 10.

Private Sub cmdAgregar_Click ()

Dim wsp as WorkSpace ‘Dimensiono las variables

Dim Base as Database

Dim Agenda as Recordset

Set wsp = DbEngine.Workspaces(0) ‘Seteo el espacio de trabajo

Set Base = wsp.OpenDatabase (“Clientes.mdb”) ‘Abro la base de Datos

Set Agenda = BasedeDatos.OpenRecordset(“SELECT * FROM Agenda WHERE Agenda.Codigo < 10”) ‘Abro el Recordset

If Not Agenda.EOF ‘si encontré algún registro

Agenda.MoveFirst ‘me muevo al primer registro

Do While Not Agenda.EOF ‘mientras no sea fin de archivo

Agenda.Delete ‘elimino el registro

Agenda.MoveNext ‘me desplazo al siguiente registro

Loop

End If

Agenda.Close ‘Cierro el Recordset

Base.Close ‘Cierro la base de Datos

Wsp.Close ‘Cierro el espacio de trabajo

End Sub

14.9- Edit (Objeto Recordset)

Aplicable a :

259

Page 260: Programacion en Java

Objeto Recordset.

Copia el registro activo de un objeto Recordset al búfer de copia para su posterior edición.

Sintaxis

recordset.Edit

El recordset representa el nombre de un objeto Recordset abierto y que se puede actualizar que contiene el registro que desea modificar.

Comentarios

Una vez que utiliza el método Edit, los cambios realizados en los campos del registro activo son copiados al búfer de copia. Después de realizar los cambios deseados en el registro, utilice el método Update para guardar los cambios.

El registro activo permanece activo después de utilizar el método Edit.

Precaución Si modifica un registro y a continuación pasa a otro registro sin utilizar antes Update, los cambios se perderán sin previo aviso. Además, si cierra recordset o finaliza el procedimiento que declara el Recordset o el objeto Database o Connection, el registro modificado se descarta sin previo aviso.

La utilización de Edit produce un error bajo las siguientes condiciones:

• No hay ningún registro activo.• El objeto Database o Recordset se abrió de sólo lectura.• No hay campos que se pueden actualizar en el registro.• El objeto Database o Recordset se abrió para uso en modo exclusivo por otro usuario

(espacio de trabajo Microsoft Jet).

Nota Para agregar, modificar o eliminar un registro, debe tener un índice único en el registro en el origen de datos de base. Si no es así, se producirá un error "Permiso denegado" en la llamada al método AddNew, Delete o Edit en un espacio de trabajo Microsoft Jet, o se producirá un error "Argumento no válido" el la llamada al método Update en un espacio de trabajo ODBCDirect.

Ejemplo de método Edit

Se necesita asignar la zona 1 a todos los registros de la tabla Agenda (dentro de la base de datos Clientes) cuyos códigos de Clientes sean menores a 20.

Private Sub cmdAgregar_Click ()

Dim wsp as WorkSpace ‘Dimensiono las variables

Dim Base as Database

Dim Agenda as Recordset

Set wsp = DbEngine.Workspaces(0) ‘Seteo el espacio de trabajo

260

Page 261: Programacion en Java

Set Base = wsp.OpenDatabase (“Clientes.mdb”) ‘Abro la base de Datos

Set Agenda = BasedeDatos.OpenRecordset(“SELECT * FROM Agenda WHERE Agenda.Codigo < 20”) ‘Abro el Recordset

If Not Agenda.EOF ‘si encontré algún registro

Agenda.MoveFirst ‘me muevo al primer registro

Do While Not Agenda.EOF ‘mientras no sea fin de archivo

Agenda.Edit ‘Edito el registro

Agenda!Zona = 1

Agenda.Update

Agenda.MoveNext ‘me desplazo al siguiente registro

Loop

End If

Agenda.Close ‘Cierro el Recordset

Base.Close ‘Cierro la base de Datos

Wsp.Close ‘Cierro el espacio de trabajo

End Sub

14.10- Hide

Aplicable a:

Objeto Form.

Oculta un objeto Form pero no lo descarga.

Sintaxis

objeto.Hide

El marcador de posición objeto representa una expresión de objeto cuyo resultado es un objeto de la lista Aplicable a. Si se omite objeto, se supone que objeto es el formulario que tenga el enfoque.

Comentarios

Cuando se oculta un formulario, se quita de la pantalla y su propiedad Visible queda establecida a False. Los controles de un formulario oculto no son accesibles para el usuario, pero están

261

Page 262: Programacion en Java

disponibles para la aplicación de Visual Basic en ejecución y para otros procesos que se estén comunicando con la aplicación mediante DDE, así como para los eventos del control Timer.

Cuando se oculta un formulario, el usuario no puede interactuar con la aplicación hasta que el código del procedimiento de evento que ha provocado la ocultación del formulario haya terminado de ejecutarse.

Si el formulario no está cargado cuando se llama al método Hide, el método Hide carga el formulario pero no lo presenta.

Ejemplo del método Hide

Este ejemplo utiliza el método Hide para ocultar un formulario. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario, y después presione F5 y haga clic en el formulario.

Private Sub Form_Click ()

Dim Msg ' Declara variable.

Hide ' Oculta el formulario.

Msg = "Haga clic en Aceptar para que vuelva a aparecer el formulario."

MsgBox Msg ' Muestra el mensaje.

Show ' Muestra de nuevo el formulario.

End Sub

14.11-MoveFirst - MoveLast - MoveNext – MovePrevious (Objeto Recordset)

Aplicable a:

Objeto Recordset.

Mueven al registro primero, último, siguiente o anterior de un objeto Recordset y lo convierten en el registro activo.

Sintaxis

recordset.{MoveFirst | MoveLast [dbRunAsync] | MoveNext | MovePrevious}

El marcador de posición recordset es una variable de objeto que representa un objeto Recordset abierto.

Comentarios

Precaución Si modifica el registro activo, utilice el método Update para guardar los cambios antes de ir a otro registro. Si va a otro registro sin actualizar, los cambios se perderán sin previo aviso.

262

Page 263: Programacion en Java

Al abrir un Recordset, el primer registro está activo y la propiedad BOF es False. Si el Recordset no contiene registros la propiedad BOF es True y no habrá ningún registro activo.

Si el primer o el último registro está activo cuando utiliza MoveFirst o MoveLast, el registro activo no cambia.

Si utiliza MovePrevious cuando el primer registro está activo, la propiedad BOF es True y no habrá ningún registro activo. Si vuelve a utilizar MovePrevious se producirá un error y BOF será True.

Si utiliza MoveNext cuando el último registro está activo, la propiedad EOF es True y no habrá ningún registro activo. Si vuelve a utilizar MoveNext se producirá un error y EOF será True.

Si recordset hace referencia a un objeto Recordset de tipo Table (sólo espacios de trabajo Microsoft Jet), el movimiento seguirá el índice activo. Puede establecer el índice activo utilizando la propiedad Index. Si no establece el índice activo, el orden de los registros devueltos no estará definido.

Importante Puede utilizar el método MoveLast para llenar completamente un objeto Recordset de tipo Dynaset o Snapshot para obtener el número de registros activos en el Recordset. Sin embargo, si utiliza MoveLast puede hacer más lentas sus aplicaciones. Sólo debe utilizar MoveLast para obtener un total de registros si es absolutamente necesario obtener un total exacto en un Recordset abierto recientemente.

No puede utilizar los métodos MoveFirst, MoveLast y MovePrevious en un Recordset de tipo Forward-only.

Para mover la posición del registro activo en un objeto Recordset un número específico de registros hacia adelante o hacia atrás, utilice el método Move.

Ejemplo de métodos MoveFirst, MoveLast, MoveNext, MovePrevious

Para un ejemplo de uso de los métodos MoveFirst y MoveNext, mire el ejemplo del método Edit.

Se necesita asignar la zona 1 a todos los registros de la tabla Agenda (dentro de la base de datos Clientes) cuyos códigos de Clientes sean menores a 20. Es necesario recorrer la tabla de atrás hacia adelante.

Private Sub cmdAgregar_Click ()

Dim wsp as WorkSpace ‘Dimensiono las variables

Dim Base as Database

Dim Agenda as Recordset

Set wsp = DbEngine.Workspaces(0) ‘Seteo el espacio de trabajo

Set Base = wsp.OpenDatabase (“Clientes.mdb”) ‘Abro la base de Datos

Set Agenda = BasedeDatos.OpenRecordset(“SELECT * FROM Agenda WHERE Agenda.Codigo < 20”) ‘Abro el Recordset

263

Page 264: Programacion en Java

If Not Agenda.EOF ‘si encontré algún registro

Agenda.MoveLast ‘me muevo al último registro

Do While Not Agenda.BOF ‘mientras no sea principio de archivo

Agenda.Edit ‘Edito el registro

Agenda!Zona = 1

Agenda.Update

Agenda.MovePrevious ‘me desplazo al registro anterior

Loop

End If

Agenda.Close ‘Cierro el Recordset

Base.Close ‘Cierro la base de Datos

Wsp.Close ‘Cierro el espacio de trabajo

End Sub

14.12- OpenDatabase (Objeto Workspace)

Aplicable a:

Objeto DBEngine, Objeto Workspace.

Abre una base de datos especificada en un objeto Workspace y devuelve una referencia al objeto Database que la representa.

Sintaxis

Set basededatos = espaciodetrabajo.OpenDatabase (nombrebasededatos, opciones, sólolectura, conexión)

La sintaxis del método OpenDatabase consta de las siguientes partes.

Argumento Descripción

basededatos Una variable de objeto que representa el objeto Database que va a abrir.

264

Page 265: Programacion en Java

espaciodetrabajo Opcional. Una variable de objeto que representa el objeto Workspace existente que

contendrá la base de datos. Si no incluye un valor para espaciodetrabajo,

OpenDatabase utiliza el espacio de trabajo predeterminado.

Nombrebasededatos Un tipo de datos String que es el nombre de un archivo de base de datos Microsoft Jet

existente o el nombre del origen de datos (DSN) de un origen de datos ODBC

existente.

opciones Opcional. Un tipo de datos Variant que establece varias opciones para la base de

datos, como se especifica en Valores.

sólolectura Opcional. Un valor de tipo de datos Variant (subtipo Boolean) que es True si desea

abrir la base de datos con acceso de sólo lectura o False (predeterminado) si desea

abrir la base de datos con acceso de lectura/escritura.

conexión Opcional. Un tipo de datos Variant (subtipo String) que especifica información

variada sobre la conexión, incluyendo las contraseñas.

Valores

Para los espacios de trabajo Microsoft Jet, puede utilizar los siguientes valores para el argumento opciones:

Valor Descripción

True Abre la base de datos en modo exclusivo.

False (Predeterminado) Abre la base de datos en modo compartido.

Comentarios

Cuando abre una base de datos, automáticamente se agrega a la colección Databases.

Estas son algunas consideraciones que debe aplicar cuando utilice nombrebasededatos:

265

Page 266: Programacion en Java

• Si hace referencia a una base de datos que ya está abierta para acceso en modo exclusivo por otro usuario, se produce un error.

• Si no hace referencia a una base de datos existente, se produce un error.

El argumento conexión se expresa en dos partes: el tipo de base de datos, seguido por punto y coma (;) y los argumentos opcionales. Primero debe proporcionar el tipo de base de datos, como "ODBC;" o "FoxPro 2.5;". A continuación, os argumentos opcionales sin un orden concreto, separados por punto y coma. Uno de los parámetros puede ser la contraseña (si hay alguna asignada). Por ejemplo:

"FoxPro 2.5; pwd=micontraseña"

Para cerrar una base de datos y, de este modo, quitar el objeto Database de la colección Databases, utilice el método Close en el objeto .

Ejemplo método OpenDatabase

Es válido el mismo ejemplo utilizado para los métodos MoveFirst, MoveLast, MoveNext, MovePrevious.

14.13- OpenRecordset (Objeto Database)

Aplicable a:

Objeto Database.

Crea un nuevo objeto Recordset y lo añade a la colección Recordsets.

Sintaxis

Set variable = objeto.OpenRecordset (origen, tipo, opciones, bloquearmodificaciones)

La sintaxis del método OpenRecordset consta de las siguientes partes.

Argumento Descripción

variable Una variable de objeto que representa el objeto Recordset que desea abrir.

Objeto Una variable de objeto que representa un objeto existente desde el que desea crear

el objeto Recordset nuevo.

Origen Un tipo de datos String que especifica el origen de los registros para el nuevo

Recordset. El origen puede ser un nombre de tabla, un nombre de consulta o una

instrucción SQL que devuelve registros.

266

Page 267: Programacion en Java

Tipo Opcional. Una constante que indica el tipo de objeto Recordset a abrir, como se

especifica en Valores.

Opciones Opcional. Una combinación de constantes que especifican las características del

objeto Recordset nuevo, como se especifica en Valores.

Bloquearmodificaciones Opcional. Una constante que determina el bloqueo para el objeto Recordset,

como se especifica en Valores.

Valores

Puede utilizar una de las siguientes constantes para el argumento tipo.

Constante Descripción

dbOpenTable Abre un objeto Recordset de tipo Table (sólo espacios de trabajo Microsoft Jet).

dbOpenDynaset Abre un objeto Recordset de tipo Dynaset (actualizable).

DbOpenSnapshot Abre un objeto Recordset de tipo Snapshot (sólo lectura)

DbOpenForwardOnly Abre un objeto Recordset de tipo Forward-only.

Nota Si abre un objeto Recordset en un espacio de trabajo Microsoft Jet y no especifica un tipo, el método OpenRecordset crea una objeto Recordset de tipo Table, si es posible. Si especifica una tabla vinculada o una consulta, el método OpenRecordset crea un objeto Recordset.de tipo Dynaset.

Puede utilizar una combinación de las siguientes constantes para el argumento opciones:

-DbAppendOnly: Permite al usuario anexar registros nuevos al objeto Recordset, pero impide la modificación o eliminación de registros existentes (sólo objetos Recordset de tipo Dynaset de Microsoft Jet).

-DbSeeChanges: Genera un error en tiempo de ejecución si otro usuario está cambiando los datos que usted está modificando.(Sólo en objetos Recordset de tipo Snapshot de Microsoft Jet). Esto es útil en aplicaciones donde varios usuarios tiene acceso de lectura/escritura simultáneo a los mismos datos.

-DbDenyWrite: Previene que otros usuarios puedan modificar o agregar registros (sólo objetos Recordset de Microsoft Jet).

-DbDenyRead: Previene que otros usuarios puedan leer datos de una tabla (sólo objetos Recordset de tipo Table de Microsoft Jet).

267

Page 268: Programacion en Java

-DbForwardOnly: Crea un objeto Recordset de tipo Forward-only (sólo objetos Recordset de tipo Snapshot de Microsoft Jet). Se proporciona sólo para compatibilidad con versiones anteriores y debe utilizar la constante dbOpenForwardOnly en el argumento tipo en vez de utilizar esta opción.

-DbReadOnly : Previene que otros usuarios puedan hacer cambios el objeto Recordset (sólo Microsoft Jet). La constante dbReadOnly en el argumento bloquearmodificaciones reemplaza esta opción, la cual se proporciona para compatibilidad con versiones anteriores.

-dbInconsistent : Permite actualizaciones inconsistentes (sólo objetos Recordset de tipo Dynaset de Microsoft Jet).

-DbConsistent : Permite sólo actualizaciones consistentes (sólo objetos Recordset de tipo Dynaset de Microsoft Jet).

Nota Las constantes dbConsistent y dbInconsistent se excluyen mutuamente y el uso de ambos produce un error. Proporcionar un argumento bloquearmodificaciones cuando el argumento opciones utiliza la constante dbReadOnly también produce un error.

Puede utilizar las siguientes constantes para el argumento bloquearmodificaciones:.

-dbReadOnly: Previene que los usuarios hagan cambios al Recordset. Puede utilizar dbReadOnly en el argumento opciones o en el argumento bloquearmodificaciones, pero nunca en ambos. Si lo utiliza en ambos argumentos, se produce un error en tiempo de ejecución.

-DbPessimistic: Utiliza el bloqueo pesimista para determinar cómo se pueden hacer cambios al objeto Recordset en un entorno multiusuario. La página que contiene el registro que está modificando está bloqueada mientras utiliza el método Edit (predeterminado en espacios de trabajo Microsoft Jet).

-DbOptimistic: Utiliza el bloqueo optimista para determinar cómo se pueden hacer cambios al objeto Recordset en un entorno multiusuario. La página que contiene el registro que está modificando está bloqueada mientras se ejecuta el método Update.

Comentarios

En un espacio de trabajo Microsoft Jet, si objeto hace referencia a un objeto QueryDef o Recordset de tipo Dynaset o Snapshot o si origen hace referencia a una instrucción SQL o un TableDef que representa una tabla adjunta, no podrá utilizar dbOpenTable para el argumento tipo y si lo hace, se producirá un error interceptable.

Si objeto hace referencia a un Recordset de tipo Dynaset o Snapshot, el Recordset nuevo es del mismo tipo objeto. Si objeto hace referencia a un objeto Recordset de tipo Table, el tipo del objeto nuevo es un objeto Recordset de tipo Dynaset. No puede abrir objetos Recordset nuevos desde objetos Recordset de tipo Forward-only.

Utilice la constante dbSeeChanges en espacio de trabajo Microsoft Jet si desea captar los cambios realizados mientras dos o más usuarios están modificando o eliminando el mismo registro. Por ejemplo, si dos usuarios empiezan a modificar el mismo registro, el primer usuario que ejecute el método Update consigue realizar la operación. Cuando el segundo usuario ejecute el método Update ocurre un error de tiempo de ejecución. Del mismo modo, si el segundo usuario intenta utilizar el método Delete para eliminar un registro y el primer usuario ha cambiado ya el mismo, se produce un error de tiempo de ejecución.

268

Page 269: Programacion en Java

En general, si al usuario se le presenta este error mientras está actualizando, su código debe actualizar el contenido de los campos y leer los valores recientemente modificados. Si se produce el error durante el proceso de eliminación, el código puede mostrar al usuario los nuevos datos del registro junto con un mensaje que indica que se han modificado recientemente los datos. En este momento, el código puede solicitar una confirmación de que el usuario desea aún eliminar el registro.

Al cerrar un Recordset utilizando el método Close, se eliminará automáticamente de la colección Recordsets.

Ejemplo del método OpenRecordset

Es válido el mismo ejemplo utilizado para los métodos MoveFirst, MoveLast, MoveNext, MovePrevious.

14.14- RemoveItem

Aplicable a:

Control ComboBox, Control ListBox.

Quita un elemento de un control ListBox o ComboBox. No acepta argumentos con nombre.

Sintaxis

objeto.RemoveItem índice

La sintaxis del método RemoveItem consta de las siguientes partes:

Parte Descripción

objeto Requerido. Una expresión de objeto cuyo resultado es un objeto de la lista Aplicable a.

índice Requerido. Un entero que especifica la posición dentro del objeto del elemento o la fila que se va a quitar. Para los primeros elementos de los controles ListBox o ComboBox, índice es 0.

Comentarios

Los controles ListBox o ComboBox que están enlazados a un control Data no aceptan el método RemoveItem.

Ejemplo del método RemoveItem

Este ejemplo utiliza el método RemoveItem para quitar entradas de un cuadro de lista. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario que contenga un control ListBox llamado List1, y después presione F5 y haga clic en el formulario.

Private Sub Form_Click ()

Dim Entry, I, Msg ' Declara variables.

269

Page 270: Programacion en Java

Msg = "Haga clic en Aceptar para agregar 100 elementos al cuadro de lista."

MsgBox Msg ' Muestra el mensaje.

For I = 1 To 100 ' Cuenta de 1 a 100.

Entry = "Entrada " & I ' Crea la entrada.

List1.AddItem Entry ' Agrega la entrada.

Next I

Msg = "Haga clic en Aceptar para quitar una de cada dos entradas."

MsgBox Msg ' Muestra el mensaje.

For I = 1 To 50 ' Determina cómo quitar

List1.RemoveItem I ' uno de cada dos

Next I ' elementos.

Msg = "Haga clic en Aceptar para quitar todos los elementos del cuadro de lista."

MsgBox Msg ' Muestra el mensaje.

List1.Clear ' Borra el cuadro de lista.

End Sub

14.15- RepairDatabase (Objeto DBEngine)

Aplicable a:

Objeto DBEngine.

Intenta reparar una base de datos dañada que accede a base de datos Microsoft Jet.

Sintaxis

DBEngine.RepairDatabase nombrebasededatos

El argumento nombrebasededatos representa un tipo de datos String que es la ruta de acceso y el nombre de un archivo de base de datos del motor Microsoft Jet existente. Si se omite la ruta, sólo se buscará en el directorio activo.

270

Page 271: Programacion en Java

Comentarios

Debe cerrar la base de datos especificada por nombrebasededatos antes de repararla. En un entorno multiusuario, los demás usuarios no podrán tener abierto nombrebasededatos mientras usted la repara. Si no está cerrado nombrebasededatos o no está disponible para uso exclusivo, se producirá un error.

Este método intenta reparar una base de datos marcada como posiblemente dañada por una operación de escritura incompleta. Esto puede ocurrir si una aplicación que utiliza el motor de base de datos Microsoft Jet termina inesperadamente debido a un corte en el suministro eléctrico o un problema de hardware. La base de datos no se marcará como posiblemente dañada si utiliza el método Close o si sale de la aplicación de una manera normal.

El método RepairDatabase también intenta validar todas las tablas del sistema y todos los índices. Se descartan los datos que no se puedan reparar. Si no se puede reparar la base de datos, se produce un error interceptable.

Cuando intente abrir o compactar una base de datos dañada, normalmente se producirá un error interceptable. En algunas situaciones, sin embargo, puede que no se detecte una base de datos dañada y no se produzca ningún error. Es conveniente ofrecer a los usuarios un método de ejecutar el método RepairDatabase en su aplicación, si la base de datos se comporta de manera impredecible.

Algunos tipos de bases de datos se pueden dañar si un usuario termina una aplicación sin cerrar los objetos Database o Recordset y el motor de base de datos Microsoft Jet, Microsoft Windows no tienen la oportunidad de vaciar las memorias de caché de datos. Para evitar que se dañen las bases de datos, establezca procedimientos para cerrar las aplicaciones y apagar los sistemas que aseguren que todas las páginas de la memoria caché están guardadas en la base de datos. En algunos casos, puede que sean necesarias fuentes de alimentación ininterrumpida para evitar pérdidas de datos por las fluctuaciones del suministro eléctrico.

Nota Después de reparar una base de datos, también es conveniente compactar la misma utilizando el método CompactDatabase para defragmentar el archivo y recuperar espacio en disco.

Ejemplo del método RepairDatabase

Este ejemplo intenta reparar la base de datos llamada Neptuno.mdb.

Sub RepairDatabaseX()

Dim errBucle As Error

If MsgBox("¿Desea reparar la base de datos Neptuno?", vbYesNo) = vbYes Then

DBEngine.RepairDatabase "Neptuno.mdb"

MsgBox "¡Fin del procedimiento reparar!"

End If

271

Page 272: Programacion en Java

End Sub

14.16- SetFocus

Aplicable a

Control CheckBox, Control ComboBox, Control CommandButton, Objeto Form Controles HScrollBar y VScrollBar, Control ListBoxControl OptionButton, Control PictureBox, Control TextBox.

Mueve el enfoque al control o formulario especificado.

Sintaxis

objeto.SetFocus

El marcador de posición objeto representa una expresión de objeto que da como resultado un objeto de la lista Aplicable a.

Comentarios

El objeto debe ser un objeto Form o un control que pueda recibir el enfoque. Después de invocar el método SetFocus, cualquier entrada del usuario se dirige al formulario o al control especificado.

El enfoque sólo se puede mover a un formulario o un control visible. Como un formulario y los controles de un formulario no son visibles hasta que el evento Load del formulario ha terminado, no puede usar en su propio evento Load el método SetFocus para mover el enfoque al formulario que se está cargando a menos que use primero el método Show para mostrar el formulario antes de que el procedimiento de evento Form_Load haya terminado.

Tampoco puede mover el enfoque a un formulario o un control si su propiedad Enabled es False. Si la propiedad Enabled se ha establecido a False en tiempo de diseño, primero debe establecerla a True antes de poder recibir el enfoque mediante el método SetFocus.

Ejemplo del método SetFocus

Al hacer click en un botón de comando, setea el foco a la caja de texto txtCodigo

Private Sub cmd1_Click()

TxtCodigo.Setfocus

End Sub

14.17- Show

Aplicable a:

Objeto Form.

Sintaxis

272

Page 273: Programacion en Java

objeto.Show estilo, formulario_propietario

La sintaxis del método Show consta de las siguientes partes:

Parte Descripción

objeto Opcional. Una expresión de objeto cuyo resultado es un objeto de la lista Aplicable a. Si se omite objeto, se supone que objeto es el formulario asociado con el módulo de formulario activo.

estilo Opcional. Un entero que determina si el formulario es modal o no modal. Si estilo es 0, el formulario es no modal; si estilo es 1, el formulario es modal.

formulario_propietario Opcional. Una expresión de cadena que especifica el componente que "posee" el formulario que se muestra. Para los formularios estándar de Visual Basic, utilice la palabra clave Me.

Comentarios

Si el formulario especificado no está cargado cuando se invoca el método Show, Visual Basic lo carga automáticamente.

Cuando Show presenta un formulario no modal, continúa con la ejecución del código que haya a continuación. Cuando Show presenta un formulario modal, el código que hay a continuación no se ejecuta hasta que el formulario se oculta o se descarga.

Cuando Show presenta un formulario modal, no hay entradas (de teclado o del mouse) excepto sobre los objetos del formulario modal. El programa debe ocultar o descargar los formularios modales (normalmente como respuesta a alguna acción del usuario) antes de que pueda producirse la entrada en otro formulario.

Aunque los demás formularios de la aplicación están deshabilitados cuando se presenta un formulario modal, los de las demás aplicaciones no lo están.

El formulario inicial de una aplicación se muestra automáticamente después de invocar su evento Load.

Ejemplo del método Show

Este ejemplo utiliza el método Show para mostrar un formulario oculto. Para probar este ejemplo, pegue el código en la sección Declaraciones de un formulario y después presione F5 y haga clic en el formulario.

Private Sub Form_Click ()

Dim Msg ' Declara variable.

Hide ' Oculta el formulario.

Msg = "Haga clic en Aceptar para que vuelva a aparecer el formulario."

273

Page 274: Programacion en Java

MsgBox Msg ' Muestra el mensaje.

Show ' Muestra de nuevo el formulario.

End Sub

14.18- Update (Objeto recordset)

Aplicable a

Objeto Recordset.

Guarda el contenido del búfer de copia en un objeto Recordset de tipo Dynaset o Table especificado.

Sintaxis

recordset.Update (tipo, obligar )

La sintaxis del método Update tiene las siguientes partes.

Parte Descripción

Recordset Una variable de objeto que representa un objeto Recordset abierto que se puede actualizar.

Tipo Opcional. Una constante que indica el tipo de actualización, como se especifica en Valores

(sólo espacios de trabajo ODBCDirect).

Obligar Opcional. Un valor de tipo Boolean que indica si se pueden o no obligar los cambios en la base

de datos, sin tener en cuenta si los datos base se han cambiado por otro usuario desde la

llamada al método AddNew, Delete o Edit. Si es True, los cambios se fuerzan y los cambios

hechos por otros usuarios se sobrescriben. Si es False (predeterminado), los cambios hechos

por otros usuarios mientras la actualización está pendiente provocarán que falle la

actualización para aquellos cambios conflictivos.

Comentarios

274

Page 275: Programacion en Java

Utilice Update para guardar el registro activo y los cambios que haya efectuado en él.

Precaución Los cambios realizados en el registro activo se perderán si:

• Utiliza el método Edit o AddNew y a continuación, pasa a otro registro sin actualizarlo previamente mediante Update.

• Utiliza Edit o AddNew y, a continuación, vuelve a usar Edit o AddNew sin utilizar previamente Update.

• Cierra el conjunto de registros a los que hace referencia recordset sin utilizar primero Update.

• Cancela la operación Edit utilizando el método CancelUpdate.

Para modificar un registro, utilice el método Edit para copiar el contenido del registro activo al búfer de copia. Si no utiliza Edit en primer lugar, se producirá un error cuando utilice Update o intente cambiar el valor de un campo.

En un espacio de trabajo Microsoft Jet, cuando el objeto Recordset de la propiedad LockEdits establecida como True (bloqueo pesimista) en un entorno multiusuario, el registro permanecerá bloqueado desde el momento en que se utiliza Edit hasta que se ejecuta el método Update o se cancele la edición. Si la configuración de la propiedad LockEdits es False (bloqueo optimista), el registro se bloquea y se compara con el registro previamente modificado justo antes de se actualizado en la base de datos. Si ha cambiado el registro desde que utilizó el método Edit, la operación Update falla.. Para que la operación Update continúe con los cambios, utilice de nuevo el método Update. Para volver al registro, tal como lo cambió el otro usuario, actualice el registro activo usando los métodos Move 0.

Nota Para agregar, modificar o eliminar un registro, debe haber un índice único en el registro del origen de datos base. Se obtiene no lo hay, se producirá un error "Permiso denegado" en la llamada al método AddNew, Delete o Edit en un espacio de trabajo Microsoft Jet, se producirá un error "Argumento no válido" en la llamada al método Update en un espacio de trabajo ODBCDirect.

Ejemplo del método Update

Es válido el ejemplo del método AddNew.

275

Page 276: Programacion en Java

Entrada/Salida de datos

<iostream.h> archivo de cabecera

Salida ------> cout << " ...........";

Entrada ----> cin >> i; da a i un valor leído desde el teclado

cout << "su número es " << i << "\n";

(Suponiendo que i=100, mostrará: su número es 100)

Se pueden usar cualquiera de las funciones de E/S de C (scanf( ), printf( ), ...) pero se considera que estos siguen más la filosofía de C++.

Comentarios

En C++ los comentarios se definen de dos formas. Los comentarios del tipo C funcionan de la misma manera tanto en C++ como en C. Sin embargo se pueden definir también comentarios de una sola línea usando //. Cuando comienza un comentario usando //, se ignora lo que sigue hasta el final de la línea.

Miembros public y private

Los miembros de una clase pueden ser datos o funciones, que pueden definirse como públicos (accedidos desde cualquier parte del programa), protegidos o privados (sólo pueden ser accedidos por las funciones propias de la clase donde se definen) mediante las palabras public, protected y private.

Declaración de clases

Una clase puede ser definida de tres formas:

1ª) Mediante la palabra struct: por defecto todos los miembros son públicos.

struct cuadrado

{

double CalcularArea ( );

void Leerdatos (double Lado1, double Lado2);

private :

double Lado1;

Curso de C++

276

Page 277: Programacion en Java

double Lado2;

};

2ª) Mediante la palabra union: por defecto los miembros son públicos y los datos comparten espacio de memoria.

union Nombre_Persona

{

void MuestraNombre ( );

void MuestraApellido ( );

private :

char Nombre_Completo [30];

char Nombre_y_Apellido [2] [15];

};

3ª) Mediante la palabra class: los miembros son privados por defecto. Es la forma usual de declarar clases.

class vehiculo

{

int Numero_Ruedas;

int Numero_Ocupantes;

public :

void MostrarNumeroOcupantes ( );

}

Para crear un objeto en C++ primero debe definirse su forma general usando la palabra reservada class. Una clase es sintácticamente similar a una estructura.

Ejemplo: esta clase define un tipo llamado cola, que se usa para crear un objeto cola:

// esto crea la clase cola

class cola {

int c[100];

277

Page 278: Programacion en Java

int ppio, fin;

public :

void ini ( );

void meter (int i);

int sacar ( );

}

Se puede crear un objeto de este tipo usando el nombre de la clase. Por ejemplo, creamos un objeto llamado intcola del tipo cola:

cola intcola;

También se pueden crear variables cuando se está definiendo una clase, poniendo los nombres de las variables después de la llave de cierre, igual que en una estructura.

Forma general de una declaración de clase:

class nombre_clase {

datos y funciones privados;

public :

datos y funciones publicos;

} lista de nombres de objetos;

Las funciones deben ser declaradas en el interior de la estructura class, mientras que su código se define normalmente fuera de ella. A la hora de definir el código de una función miembro de una clase se le debe indicar al compilador a qué clase pertenece la función, esto se indica precediendo al nombre de la función el nombre de la clase y un par de signos de dos puntos "::". Es necesario ya que en C++ está permitido que clases distintas declaren funciones distintas pero con el mismo nombre.

Ejemplo:

void cola::meter (int i)

{

if (ppio=100) {

cout << "la cola está llena";

return;

}

278

Page 279: Programacion en Java

ppio++;

c[ppio]=i;

}

A :: se le denomina operador de resolución de ámbito.

Para llamar a una función de una clase desde una parte del programa que no sea parte de la propia clase, se debe utilizar el nombre del objeto y el operador punto "."

Ejemplo:

cola a,b;

a.ini ( );

Constructores y destructores

Es normal que una parte de un objeto necesite una inicialización antes de poder usarse. Un constructor no es más que una función miembro que aglutina un conjunto de instrucciones que permiten inicializar los objetos de una clase. El nombre de esa función debe coincidir con el nombre de la clase.

Ejemplo:

//esto crea la clase cola

class cola {

int c[100];

int ppio,fin;

public :

cola (void) //constructor

void meter (int i);

int sacar (void);

};

El constructor cola ( ) no tiene especificado tipo de dato devuelto. En C++ las funciones constructoras no pueden devolver valores.

La función cola se codifica como:

//Función constructora

279

Page 280: Programacion en Java

cola::cola (void)

{

ppio=fin=0;

cout << "cola inicializada\n";

}

En muchos casos un objeto debe realizar alguna acción o acciones cuando se destruye. Hay muchas razones por las que se puede necesitar un destructor. Por ejemplo, un objeto puede tener que liberar memoria que previamente se le ha asignado o reservado.

Un destructor recibe el mismo nombre que la clase pero precedido por el carácter "~".

El siguiente ejemplo es de la clase cola y sus funciones constructora y destructora (tenga en cuenta que la clase cola no necesita un destructor).

class cola {

int c[100];

int ppio,fin;

public :

cola (void); //constructor

~cola (void); //destructor

void meter (int i);

int sacar (void);

}

//Función constructora

cola::cola (void)

{

ppio=fin=0;

cout <<"cola inicializada\n";

}

//Función destructora

280

Page 281: Programacion en Java

cola::~cola (void)

{

cout <<"cola destruida\n";

}

Funciones inline

Una función de línea es una función que se «expande» en el punto donde se llama en vez de ser realmente llamada. Esto lo haremos cuando dicha función sea muy corta y se use mucho. Nos ahorraremos el tiempo que se pierde haciendo el pase de parámetros de una llamada a función.

Hay dos formas de declarar una función como inline:

1ª) Precediendo la definición de una función con la palabra clave inline.

inline <declaración de función>

2ª) Para las funciones miembro de una clase, se puede hacer definiendo el código de la función dentro de la definición de la clase.

class <nombre_de_clase>

{

<declaración_de_función> {<código de la función>}

}

Funciones friend

Es posible que una función que no es miembro de una clase tenga acceso a la parte privada de esa clase declarándola como friend (amiga) de la clase.

El formato de la declaración de funciones friend es el siguiente:

class <nombre de la clase>

{

public :

friend <declaración de función>

}

Calificación de variables miembro

281

Page 282: Programacion en Java

A veces es necesario distinguir entre variables miembro de una clase y otro tipo de variables. Esto se puede realizar mediante el operador de resolución de ámbito "::".

Ejemplo:

class X

{

int m;

public :

void Setm (int);

void Getm (void) {cout <<m;}

};

void main( )

{

X x;

x.Setm (5);

x.Getm ( );

}

void X::Setm (int m)

{

X::m=m; //para distinguir el parámetro m, de miembro m de la clase X

}

Variables de clase

Entre los datos que pueden ser declarados en una clase se pueden hacer dos distinciones:

- Las variables de instancia representan campos con denominación común para todos los objetos de la clase, pero con un contenido particular para cada uno de ellos.

- Una variable de clase es un campo con idéntico nombre e idéntico contenido para todos los objetos de una clase. Es más, la modificación del contenido de ese campo en un objeto, afectará a todos los demás objetos. En realidad una variable de clase no es un campo que se halle en todos los objetos de una clase y que tenga el mismo contenido para todos ellos, sino que es un mismo espacio de memoria que es compartido por todos los objetos de una clase.

282

Page 283: Programacion en Java

La forma de declarar una variable de clase en C++ es declarando un campo miembro como static.

class <nombre_clase>

{

..............................

static <tipo> <nombre_variable>

...............................

}

Vectores de objetos

Se pueden crear arrays de objetos de la misma manera que se crean arrays de cualquier otro tipo de datos. Por ejemplo la siguiente línea declara un vector de 10 objetos de una clase llamada X, que deberá haber sido declarada con anterioridad.

X vector[10];

En este ejemplo, en caso de existir un constructor para la clase X, éste se ejecutará 10 veces, una para cada objeto del vector. De igual manera, cuando se salga del ámbito de utilización del vector de objetos declarado, en caso de existir una función destructora de la clase, ésta se ejecutará para cada uno de los objetos del vector.

Ejemplo para pasar parámetros al constructor:

class X {

int n;

static int suma; X v[3] = {1,2,3}

X(int i) X::suma=6

{

n=i;

suma+=i;

}

}

X::suma=0;

Punteros a objetos

283

Page 284: Programacion en Java

De forma similar en C++ puede hacerse referencia a un objeto ya sea directamente, o bien usando un puntero a ese objeto.

Para acceder a un elemento de un objeto usando el objeto real, se usa el operador punto (.). Para acceder a un elemento específico de un objeto cuando se usa un puntero al objeto, se debe usar el operador flecha (->).

Un puntero a un objeto se declara con la misma sintaxis que con cualquier otro tipo de dato.

<Nombree_Clase> *<Nombre_Variable>;

A un puntero se le puede asignar un objeto de dos maneras:

- asignándole la dirección de un objeto existente.

- localizando espacio en memoria mediante el operador new.

Ejemplo (primer caso):

class P_ejemplo {

int num;

public:

void est_num(int val) {num=val;}

void mostrar_num();

};

void P_ejemplo :: mostrar_num()

{

cout << num << "\n";

}

main(void)

{

P_ejemplo ob, *p; //declarar un objeto y un puntero a él

ob.est_num(1); //acceso a ob directamente

ob.mostrar_num();

284

Page 285: Programacion en Java

p=&ob; //asignar a p la dirección de ob

p->mostrar_num(); // acceder a ob usando un puntero

return 0;

}

La asignación de espacio de memoria mediante el operador new tiene la característica de que al mismo tiempo que se asigna memoria, se ejecuta la función constructor de la clase.

De similar manera, cuando se ejecuta el operador delete para un objeto, además de liberar el espacio ocupado, se ejecuta la función destructora de la clase del puntero.

Ejemplo:

main(void)

{

int *p;

p=new int; //asigna memoria para un entero

if (!p) {

cout << "fallo en la asignación";

return 1;

}

*p=20; //asigna a esa memoria el valor 20

cout << *p;

delete p; //libera la memoria

return 0;

La Herencia

La herencia es el proceso por el cual un objeto puede adquirir las propiedades de otro objeto. En C++, la herencia se soporta permitiendo a una clase incorporar otra clase dentro de su declaración.

Las clases que heredan propiedades se llaman clase derivada, mientras que la clase de la que se heredan se denomina clase base.

La forma de declarar una clase derivada es:

285

Page 286: Programacion en Java

class <nombre_clase_derivada> : <acceso> <nombre_clase_base>

<acceso> puede ser public o private:

- public: los miembros public siguen siendo public, y los private siguen siendo private en la clase derivada.

- private: los miembros public y private son todos private en la clase derivada.

Ejemplo:

class X {

char nombre[80];

public :

void mostrar(void);

void nombrar(char *nom);

};

class Y : public X { //clase derivada de X

int edad;

public:

void mostrarY(void);

void poneredad(int edad);

};

Uso de la palabra protected

Se puede conceder a la clase derivada acceso a los elementos private de una clase base haciéndo a estos protected.

Ejemplo:

class X {

protected:

int i;

int j;

public:

286

Page 287: Programacion en Java

void obt_ij(void)

void poner_ij(void);

287

Page 288: Programacion en Java

class y : public X {

int k;

public:

int obj_k(void);

void hacer_k(void);

};

Da a Y acceso a i y j aunque permanezcan inaccesibles para el resto del programa.

Resumiendo: Un miembro de una clase puede ser private, protected o public.

- Si es private su nombre sólo puede ser usado por funciones miembro y friend de la clase en la cual es declarado.

- Si es protected, su nombre sólo puede ser usado por funciones miembro y friend de la clase en la cual es declarado y por funciones miembro y friend de las clases derivadas de esta clase.

- Si es public, su nombre puede ser usado por cualquier función.

Herencia múltiple

Es posible que una clase herede atributos de dos o más clases. Para realizar esto, se usa una lista de herencia, separada por comas, en la lista de las clases base de la clase derivada. La forma general es:

class nombre_clase_derivada : lista de clases base

{

..............

};

Por ejemplo en este programa Z hereda de X y de Y.

class X {

protected:

int a:

public:

void hacer_a(int i);

288

Page 289: Programacion en Java

};

class Y {

protected:

int b;

public:

289

Page 290: Programacion en Java

void hacer_b(int i);

};

class Z : public X, public Y {

public:

int hacer_ab(void);

};

Constructores y destructores en clases derivadas

Es posible que una clase base y una clase derivada tengan cada una su función de construcción. Cuando una clase derivada contiene un constructor, se ejecuta el constructor base antes de ejecutar el constructor de la clase derivada.

class Base {

public:

Base ( ) { cout <<"\n La clase Base ha sido creada";}

};

class Derivada : public Base {

public:

Derivada ( ) { cout << " La clase Derivada ha sido creada"; }

};

main ( )

{

Derivada der;

return 0;

}

La clase Base ha sido creada

La clase Derivada ha sido creada

290

Page 291: Programacion en Java

La función de destrucción de una clase derivada se ejecuta antes que la de la clase base. Esto es así ya que la destrucción de la clase base implica la destrucción de la clase derivada, el destructor de la clase derivada debe ser ejecutado antes de ser destruido.

Cuando la herencia es múltiple los constructores de las clases base se invocan por órden, de izquierda a derecha y siempre antes que el de la clase derivada. Mientras que los destructores se invocan de derecha a izquierda, y siempre después que el destructor de la clase derivada.

Clase base virtual

Cuando dos objetos o más se derivan de una clase base común, se puede impedir que estén presentes múltiples copias de la clase base en un objeto derivado de esos objetos, declarando la clase base como virtual cuando se hereda.

#include <iostream.h>

class base {

public:

int i;

};

class d1 : virtual public base { // d1 hereda base como virtual

public:

int j;

};

class d2 : virtual public base { // d2 hereda base como virtual

public:

int k;

};

class d3 : public d1, public d2 { // d3 hereda d1 y d2 pero solo hay una

public: // copia de base en d3

int m;

};

main (void)

291

Page 292: Programacion en Java

{

d3 d;

d.i=10;

d.j=20;

d.k=30;

d.m=40;

}

En d3 sólo hay una copia de base y d.i=10 es perfectamente válido y no es ambiguo.

Polimorfismo

El fin del polimorfismo es permitir el uso de un nombre para especificar un nombre de acción general. Se ejecuta una parte específica de la clase general dependiendo del tipo de dato con el que está tratando.

Sobrecarga de funciones

Una de las maneras que tiene C++ de llegar al polimorfismo es a través de la sobrecarga de funciones. En C++, dos o más funciones pueden compartir un mismo nombre siempre y cuando difieran en la declaración de sus parámetros.

Las funciones que comparten el mismo nombre se dice que están sobrecargadas.

int Cuadrado (int i);

long Cuadrado (long l);

double Cuadrado (double x);

void main ( )

{

int i=1;

long l=3;

double x=0.45;

cout << "El cuadrado de un entero es: << Cuadrado (i) <<"\n";

cout << "El cuadrado de un long es: << Cuadrado (l) << "\n";

cout << "El cuadrado de un double es: << Cuadrado (x) << "\n";

292

Page 293: Programacion en Java

}

int Cuadrado (int i)

{

return i*i;

}

long Cuadrado (long l)

{

return l*l;

}

double Cuadrado (double x)

{

return x*x;

}

La palabra reservada this

Cada vez que se invoca una función miembro, se pasa automáticamente un puntero al objeto que la invoca. Se puede acceder a este puntero usando this. El puntero this es un parámetro implícito para todas las funciones miembro.

class c1 {

int i;

......

......

}

Una función miembro puede asignarle a i el valor 10 usando esta sentencia: i=10;

En realidad, esta sentencia es la foma corta de la sentencia this->i=10;

Sobrecarga de operadores

Para sobrecargar un operador se debe definir que operación significa con relación a la clase a la que se aplica. Para hacer esto, hay que crear una función operador que defina su acción.

293

Page 294: Programacion en Java

Forma general:

tipo nombreclase :: operator # (lista de argumentos)

{

}

# - es el operador que se quiere sobrecargar.

tipo - es el tipo de valor devuelto por la operación especificada, es con frecuencia, del mismo tipo que la clase para la cual se ha sobrecargado el operador.

El objeto causante de una llamada a un operador sobrecargado, siempre es el situado a la izquierda del operador.

En una expresión a+b donde a y b son objetos, a es el causante de la llamada (pasa su valor a través de this), mientras que el valor de b es recibido a través del paso de parámetros.

En general, cuando se usa una función miembro, no se necesitan parámetros al sobrecargar una operación monaria y solo se necesita un parámetro cuando se sobrecarga un operador binario.

Funciones operadoras amigas

Es posible que una función operador sea amiga de una clase en vez de miembro, las funciones amigas no tienen el operador implícito this por tanto cuando se usa una función amiga para sobrecargar un operador, se pasan los dos operandos cuando se está sobrecargando un operador binario y un solo operando cuando el operador es unario.

punto a;

punto b:

b=a+5;

Esta operación debe retornar un punto cuyas coordenadas serán las de a incrementadas en 5.

punto punto :: operator + (int i)

{

punto temp;

temp.x=p.x+i;

temp.y=p.y+i;

temp.z=p.z+i;

return temp;

294

Page 295: Programacion en Java

}

Esta función resolvería expresiones como a+5 pero no funcionaría si la expresión estuviese invertida, es decir, 5+a. Para resolver este problema se usa la sobrecarga de operadores mediante funciones friend, ya que podemos escpecificar el orden de todos los parámetros, porque no existen parámetros implícitos.

El problema del doble orden de los operadores de una suma, solo puede ser resuelto escribiendo dos funciones friend.

punto operator + (punto p, int i)

{

punto temp;

temp.x=p.x+i;

temp.y=p.y+i;

temp.z=p.z+i;

return temp;

}

punto operator + (int i, punto p)

{

punto temp;

temp.x=p.x+i;

temp.y=p.y+i;

temp.z=p.z+i;

return temp;

}

Punteros a clases derivadas

En C++ el polimorfismo se admite tanto en el momento de la compilación, como en el de la ejecución. La sobrecarga de operadores y funciones es un ejemplo de polimorfismo en el momento de la compilación. Para conseguir el polimorfismo en el momento de la ejecución C++ permite usar clases derivadas y funciones virtuales.

En C++ los punteros a clases base y a clases derivadas están relacionados. Mediante un puntero de una clase base es posible acceder a una clase derivada, aunque no es posible a la inversa. Por ejemplo:

295

Page 296: Programacion en Java

clase_B *p; clase_B tipo base

clase_B B_ob; clase_D derivado de clase_B

clase_D D_ob;

p=&B_ob; // p apunta a un objeto del tipo clase_B

p=&D_ob; // p apunta a un objeto del tipo clase_D

Usando p, se puede acceder a todos los elementos de D_ob heredados de B_ob. Sin embargo, los elementos específicos de la D_ob no pueden ser referenciados usando p, a menos que se utilice una refundición de tipos.

class X {

protected:

char nombre[30];

public:

void mostrar (void);

void nombrar (char *nom);

};

class Y : public X {

int edad;

public:

void mostrarY (void);

void poneredad (int ed);

};

main ( )

{

X x;

Y y;

X *px;

296

Page 297: Programacion en Java

Y *py;

((Y *) px)-> poneredad (16) // Accedemos a los no miembros de X

((Y *) px)-> mostrarY ( ) // refundiendo el tipo del puntero

Funciones virtuales

Una función virtual es una función que se declara en la clase base como virtual y se redefine en una o más clases derivadas.

Las funciones virtuales son especiales, porque cuando se accede a una usando un puntero de la clase base a un objeto de una clase derivada, C++ determinada en tiempo de ejecución a que función llamar en función del tipo de objeto apuntado.

Una función virtual se declara como virtual dentro de la clase base usando la palabra clave virtual. Sin embargo, cuando se redefine una función virtual en una clase derivada no se necesita repetir la palabar clave virtual (aunque no es un error hacerlo).

class Base {

public:

virtual void quien ( ) { cout << "Base\n"; };

};

class primera_d : public Base {

public:

void quien ( ) { cout << "Primera derivación\n"; };

};

class segunda_d : public Base {

};

297

Page 298: Programacion en Java

main ( )

{

Base obj_base;

Base * p;

primera_d obj_primera;

segunda_d obj_segunda;

p=&obj_base;

p->quien ( ); // Se accede al quien de Base

p=&obj_primera;

p->quien ( ); // Se accede al quien de primera

p=&obj_segunda;

p->quien ( ); // Se accede al quien de base

}

Funciones virtuales puras y tipos abstractos

Una función virtual pura es una función que se declara en una clase base y que no tiene definición relativa a la base, por lo que todos los tipos derivados se ven obligados a definir su propia versión de esa función.

Para declarar una función virtual pura se utiliza la forma general:

virtual tipo nombre_funcion (lista parametros) = 0;

Si una clase tiene por lo menos una función virtual pura, entonces se dice que esa clase es abstracta. Las clases abstractas tienen una característica importante: no puede haber objetos de esa clase, porque una o más de sus funciones carecen de definición. Es posible declarar punteros de una clase abstracta.

class figura {

protected:

double x,y;

public:

void pon_dim (double i, double j) { x=i ; y=j; };

298

Page 299: Programacion en Java

virtual void mostrar_area ( ) = 0;

};

class triangulo : public figura {

public:

void mostrar_area ( )

{

cout << "Triangulo de altura" << x << " y base" << y;

};

};

class cuadrado : public figura {

public:

void mostrar_area ( )

{

cout << "Cuadrado de dimensión << x << "," << y;

};

};

main ( )

{

figura *p;

triangulo trian;

cuadrado cuad;

p=&trian;

p->pon_dim (10.0, 5.0);

p->mostrar_area ( );

p=&cuad;

p->pon_dim (10.0, 5.0);

299

Page 300: Programacion en Java

p->mostrar_area ( );

}

300

Page 301: Programacion en Java

Curso de Assembler

Sistema de Cómputo.

Le llamamos sistema de cómputo a la configuración completa de una computadora, incluyendo las unidades periféricas y la programación de sistemas que la hacen un aparato útil y funcional para un fin determinado.

Procesador Central.

Esta parte es conocida también como unidad central de procesamiento o UCP. formada a su vez por la unidad de control y la unidad aritmética y lógica. Sus funciones consisten en leer y escribir contenidos de las celdas de memoria, llevar y traer datos entre celdas de memoria y registros especiales y decodificar y ejecutar las instrucciones de un programa.

El procesador cuenta con una serie de celdas de memoria que se utilizan con mucha frecuencia y que, por ende, forman parte de la UCP. Estas celdas son conocidas con el nombre de registros. Un procesador puede tener una docena o dos de estos registros. La unidad aritmética y lógica de la UCP realiza las operaciones relacionadas con los cálculos numéricos y simbólicos. Típicamente estas unidades sólo tienen capacidad de efectuar operaciones muy elementales como: suma y resta de dos números de punto fijo, multiplicación y división de punto fijo, manipulación de bits de los registros y comparación del contenido de dos registros.

Las computadoras personales pueden clasificarse por lo que se conoce como tamaño de palabra, esto es, la cantidad de bits que el procesador puede manejar a la vez.

Memoria Central.

Es un conjunto de celdas (actualmente fabricadas con semiconductores) usadas para procesos generales, tales como la ejecución de programas y el almacenamiento de información para las operaciones.

Cada una de las celdas puede contener un valor numérico y tienen la propiedad de ser direccionables, esto es, que se pueden distinguir una de otra por medio de un número único o dirección para cada celda.

El nombre genérico de estas memorias es Random Access Memory (Memoria de acceso aleatorio) o RAM por sus siglas en inglés. La principal desventaja de este tipo de memoria es que los circuitos integrados pierden la información que tienen almacenada cuando se interrumpe la alimentación eléctrica. Esto llevó a la creación de memorias cuya información no se pierda cuando se apaga el sistema. Estas memorias reciben el nombre de Read Only Memory (Memoria de solo lectura) o ROM.

Unidades de Entrada y Salida.

Para que una computadora nos sea útil es necesario que el procesador se comunique al exterior por medio de interfaces que permiten la entrada y la salida de datos del procesador y la memoria. Haciendo uso de estas comunicaciones es posible introducir datos para su procesamiento y la posterior visualización de los datos ya procesados.

301

Page 302: Programacion en Java

Algunas de las unidades de entrada mas comunes son teclados, lectoras de tarjetas (ya en desuso), mouse, etc. Las unidades de salida mas comunes son las terminales de video y las impresoras.

Unidades de Memoria Auxiliar.

Como la memoria central de una computadora es costosa y, considerando las aplicaciones actuales, muy limitada, surge entonces la necesidad de crear sistemas de almacenamiento de información prácticos y económicos. Además, la memoria central pierde su contenido al apagarse la máquina, por lo que no es conveniente utilizarla para almacenamiento permanente de datos.

Estos y otros incovenientes dan lugar a la creación de unidades periféricas de memoria que reciben el nombre de memoria auxiliar o secundaria. De estas unidades periféricas las más comunes son las cintas y los discos magnéticos.

La información almacenada en estos medios magnéticos recibe el nombre de archivo. Un archivo está formado por un número variable de registros, generalmente de tamaño fijo; los registros pueden contener datos o programas.

Unidades de información

Para que la PC pueda procesar la información es necesario que ésta se encuentre en celdas especiales llamadas registros.

Los registros son conjuntos de 8 o 16 flip-flops (basculadores o biestables).

Un flip-flop es un dispositivo capaz de almacenar dos niveles de voltaje, uno bajo, regularmente de 0.5 volts y otro alto comunmente de 5 volts. El nivel bajo de energía en el flip-flop se interpreta como apagado o 0, y el nivel alto como prendido o 1. A estos estados se les conoce usualmente como bits, que son la unidad mas pequeña de información en una computadora.

A un grupo de 16 bits se le conoce como palabra, una palabra puede ser dividida en grupos de 8 bits llamados bytes, y a los grupos de 4 bits les llamamos nibbles.

Sistemas numéricos

El sistema numérico que utilizamos a diario es el sistema decimal, pero este sistema no es conveniente para las máquinas debido a que la información se maneja codificada en forma de bits prendidos o apagados; esta forma de codificación nos lleva a la necesidad de conocer el cálculo posicional que nos permita expresar un número en cualquier base que lo necesitemos.

Es posible representar un número determinado en cualquier base mediante la siguiente formula:

Donde n es la posición del dígito empezando de derecha a izquierda y numerando a partir de cero. D es el dígito sobre el cual operamos y B es la base numérica empleada.

Convertir números binarios a decimales

Trabajando en el lenguaje ensamblador nos encontramos con la necesidad de convertir números del sistema binario, que es el empleado por las computadoras, al sistema decimal utilizado por las personas.

302

Page 303: Programacion en Java

El sistema binario está basado en unicamente dos condiciones o estados, ya sea encendido (1) o apagado (0), por lo tanto su base es dos.

Para la conversión podemos utilizar la formula de valor posicional:

Por ejemplo, si tenemos el numero binario 10011, tomamos de derecha a izquierda cada dígito y lo multiplicamos por la base elevada a la nueva posición que ocupan:

Binario: 1 1 0 0 1Decimal: 1*2^0 + 1*2^1 + 0*2^2 + 0*2^3 + 1*2^4 = 1 + 2 + 0 + 0 + 16 = 19 decimal.

El caracter ^ es utilizado en computación como símbolo de potenciación y el caracter * se usa para representar la multiplicación.

Convertir números decimales a binarios

Existen varios métodos de conversión de números decimales a binarios; aquí solo se analizará uno. Naturalmente es mucho mas fácil una conversión con una calculadora científica, pero no siempre se cuenta con ella, así que es conveniente conocer por lo menos una forma manual para hacerlo.

El método que se explicará utiliza la división sucesiva entre dos, guardando el residuo como dígito binario y el resultado como la siguiente cantidad a dividir.

Tomemos como ejemplo el número 43 decimal.

43/2 = 21 y su residuo es 1

21/2 = 10 y su residuo es 1

10/2 = 5 y su residuo es 0

5/2 = 2 y su residuo es 1

2/2 = 1 y su residuo es 0

1/2 = 0 y su residuo es 1

Armando el número de abajo hacia arriba tenemos que el resultado en binario es 101011

Sistema hexadecimal

En la base hexadecimal tenemos 16 dígitos que van del 0 al 9 y de la letra A hasta la F (estas letras representan los números del 10 al 15). Por lo tanto, contamos 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E y F.

La conversión entre numeración binaria y hexadecimal es sencilla. Lo primero que se hace para una conversión de un número binario a hexadecimal es dividirlo en grupos de 4 bits, empezando de derecha a izquierda. En caso de que el último grupo (el que quede mas a la izquierda) sea menor de 4 bits se rellenan los faltantes con ceros.

Tomando como ejemplo el número binario 101011 lo dividimos en grupos de 4 bits y nos queda:

303

Page 304: Programacion en Java

10; 1011

Rellenando con ceros el último grupo (el de la izquierda):

0010; 1011

Después tomamos cada grupo como un número independiente y consideramos su valor en decimal:

0010 = 2; 1011 = 11

Pero como no podemos representar este número hexadecimal como 211 porque sería un error, tenemos que sustituir todos los valores mayores a 9 por su respectiva representación en hexadecimal, con lo que obtenemos:

2BH (Donde la H representa la base hexadecimal)

Para convertir un número de hexadecimal a binario solo es necesario invertir estos pasos: se toma el primer dígito hexadecimal y se convierte a binario, y luego el segundo, y así sucesivamente hasta completar el número.

Código ASCII

ASCII generalmente se pronuncia "aski", es un acrónimo de American Standard Code for Information Interchange.

Este código asigna a las letras del alfabeto, a los dígitos decimales del 0 al 9 y a varios símbolos adicionales un número binario de 7 bits (poniéndose el bit 8 en su estado de apagado o 0).

De esta forma cada letra, dígito o caracter especial ocupa un byte en la memoria de la computadora.

Podemos observar que este método de representación de datos es muy ineficiente en el aspecto numérico, ya que en formato binario nos basta un solo byte para representar numeros de 0 a 255, en cambio con el código ASCII un byte puede representar unicamente un dígito.

Debido a esta ineficiencia, el código ASCII es principalmente utilizado en la memoria para representar texto.

Metodo BCD

BCD es un acrónimo de Binary Coded Decimal.

En esta notación se utilizan grupos de 4 bits para representar cada dígito decimal del 0 al 9. Con este método podemos representar dos dígitos por byte de información.

Aœn cuando este método es mucho mas práctico para representación de números en la memoria en comparación al ASCII, todavía se queda por debajo del binario, ya que con un byte en el método BCD solo podemos representar dígitos del 0 al 99, en cambio, en formato binario podemos representar todos los dígitos desde 0 hasta 255.

304

Page 305: Programacion en Java

Este formato es utilizado principalmente para representar números muy grandes en aplicaciones mercantiles ya que facilita las operaciones con los mismos evitando errores de redondeo.

Representación de punto flotante

Esta representación esta basada en la notación científica, esto es, representar un número en dos partes: su mantisa y su exponente.

Poniendo como ejemplo el número 1234000, podemos representarlo como 1.123*10^6, en esta última notación el exponente nos indica el número de espacios que hay que mover el espacio hacia la derecha para obtener el resultado original.

En caso de que el exponente fuera negativo nos estaría indicando el número de espacios que hay que recorrer el punto decimal hacia la izquierda para obtener el original.

Proceso de creación de un programa

Para la creación de un programa es necesario seguir cinco pasos: Diseño del algoritmo, codificación del mismo, su traducción a lenguaje máquina, la prueba del programa y la depuración.

En la etapa de diseño se plantea el problema a resolver y se propone la mejor solución, creando diagramas esquemáticos utilizados para el mejor planteamiento de la solución.

La codificación del programa consiste en escribir el programa en algún lenguaje de programación; en este caso específico en ensamblador, tomando como base la solución propuesta en el paso anterior.

La traducción al lenguaje máquina es la creación del programa objeto, esto es, el programa escrito como una secuencia de ceros y unos que pueda ser interpretado por el procesador.

La prueba del programa consiste en verificar que el programa funcione sin errores, o sea, que haga lo que tiene que hacer.

La última etapa es la eliminación de las fallas detectadas en el programa durante la fase de prueba. La corrección de una falla normalmente requiere la repetición de los pasos comenzando desde el primero o el segundo.

Para crear un programa en ensamblador existen dos opciones, la primera es utilizar el MASM (Macro Assembler, de Microsoft), y la segunda es utilizar el debugger, en esta primera sección utilizaremos este último ya que se encuentra en cualquier PC con el sistema operativo MS-DOS, lo cual lo pone al alcance de cualquier usuario que tenga acceso a una máquina con estas caracteristicas.

Debug solo puede crear archivos con extensión .COM, y por las características de este tipo de programas no pueden ser mayores de 64 kb, además deben comenzar en el desplazamiento, offset, o dirección de memoria 0100H dentro del segmento específico.

Registros de la UCP

La UCP tiene 14 registros internos, cada uno de 16 bits. Los primeros cuatro, AX, BX, CX, y DX son registros de uso general y tambien pueden ser utilizados como registros de 8 bits, para utilizarlos como tales es necesario referirse a ellos como por ejemplo: AH y AL, que son los bytes

305

Page 306: Programacion en Java

alto (high) y bajo (low) del registro AX. Esta nomenclatura es aplicable también a los registros BX, CX y DX.

Los registros son conocidos por sus nombres específicos:

AX Acumulador

BX Registro base

CX Registro contador

DX Registro de datos

DS Registro del segmento de datos

ES Registro del segmento extra

SS Registro del segmento de pila

CS Registro del segmento de código

BP Registro de apuntadores base

SI Registro índice fuente

DI Registro índice destino

SP Registro del apuntador de la pila

IP Registro de apuntador de siguiente instrucción

F Registro de banderas

Es posible visualizar los valores de los registros internos de la UCP utilizando el programa Debug. Para empezar a trabajar con Debug digite en el prompt de la computadora:

C:\> Debug [Enter]

En la siguiente linea aparecera un guión, éste es el indicador del Debug, en este momento se pueden introducir las instrucciones del Debug. Utilizando el comando:

- r [Enter]

Se desplegaran todos los contenidos de los registros internos de la UCP; una forma alternativa de mostrarlos es usar el comando "r" utilizando como parametro el nombre del registro cuyo valor se quiera visualizar. Por ejemplo:

- rbx

Esta instrucción desplegará unicamente el contenido del registro BX y cambia el indicador del Debug de " - " a " : "

306

Page 307: Programacion en Java

Estando así el prompt es posible cambiar el valor del registro que se visualizó tecleando el nuevo valor y a continuación [Enter], o se puede dejar el valor anterior presionando [Enter] sin telclear ningún valor.

Es posible cambiar el valor del registro de banderas, así como utilizarlo como estructura de control en nuestros programas como se verá mas adelante. Cada bit del registro tiene un nombre y significado especial, la lista dada a continuación describe el valor de cada bit, tanto apagado como prendido y su relación con las operaciones del procesador:

Overflow

NV = no hay desbordamiento;

OV = sí lo hay

Direction

UP = hacia adelante;

DN = hacia atras;

Interrupts

DI = desactivadas;

EI = activadas

Sign

PL = positivo;

NG = negativo

Zero

NZ = no es cero;

ZR = sí lo es

Auxiliary Carry

NA = no hay acarreo auxiliar;

AC = hay acarreo auxiliar

Parity

PO = paridad non;

PE = paridad par;

307

Page 308: Programacion en Java

Carry

NC = no hay acarreo;

CY = Sí lo hay

La estructura del ensamblador

En el lenguaje ensamblador las lineas de código constan de dos partes, la primera es el nombre de la instrucción que se va a ejecutar y la segunda son los parámetros del comando u operandos. Por ejemplo:

add ah bh

Aquí "add" es el comando a ejecutar (en este caso una adición) y tanto "ah" como "bh" son los parámetros.

El nombre de las instrucciones en este lenguaje esta formado por dos, tres o cuatro letras. a estas instrucciones tambien se les llama nombres mnemónicos o códigos de operación, ya que representan alguna función que habrá de realizar el procesador.

Existen algunos comandos que no requieren parametros para su operación, as’ como otros que requieren solo un parámetro.

Algunas veces se utilizarán las instrucciones como sigue:

add al,[170]

Los corchetes en el segundo parámetro nos indican que vamos a trabajar con el contenido de la casilla de memoria número 170 y no con el valor 170, a ésto se le conoce como direccionamiento directo.

Nuestro primer programa

Vamos a crear un programa que sirva para ilustrar lo que hemos estado viendo, lo que haremos será una suma de dos valores que introduciremos directamente en el programa:

El primer paso es iniciar el Debug, este paso consiste unicamente en teclear debug [Enter] en el prompt del sistema operativo.

Para ensamblar un programa en el Debug se utiliza el comando "a" (assemble); cuando se utiliza este comando se le puede dar como parametro la dirección donde se desea que se inicie el ensamblado, si se omite el parametro el ensamblado se iniciará en la localidad especificada por CS:IP, usualmente 0100H, que es la localidad donde deben iniciar los programas con extensión .COM, y sera la localidad que utilizaremos debido a que debug solo puede crear este tipo específico de programas.

Aunque en este momento no es necesario darle un parametro al comando "a" es recomendable hacerlo para evitar problemas una vez que se haga uso de los registros CS:IP, por lo tanto tecleamos:

- a0100 [Enter]

308

Page 309: Programacion en Java

Al hacer ésto aparecerá en la pantalla algo como: 0C1B:0100 y el cursor se posiciona a la derecha de estos números, nótese que los primeros cuatro dígitos (en sistema hexagesimal) pueden ser diferentes, pero los últimos cuatro deben ser 0100, ya que es la dirección que indicamos como inicio. Ahora podemos introducir las instrucciones:

0C1B:0100 mov ax,0002 ;coloca el valor 0002 en el registro ax

0C1B:0103 mov bx,0004 ;coloca el valor 0004 en el registro bx

0C1B:0106 add ax,bx ;le adiciona al contenido de ax el contenido de bx

0C1B:0108 int 20 ; provoca la terminación del programa.

0C1B:010A

No es necesario escribir los comentarios que van despues del ";". Una vez digitado el último comando, int 20, se le da [Enter] sin escribir nada mas, para volver al prompt del debuger.

La última linea escrita no es propiamente una instrucción de ensamblador, es una llamada a una interrupción del sistema operativo, estas interrupciones serán tratadas mas a fondo en un capítulo posterior, por el momento solo es necesario saber que nos ahorran un gran número de lineas y son muy útiles para accesar a funciones del sistema operativo.

Para ejecutar el programa que escribimos se utliza el comando "g", al utilizarlo veremos que aparece un mensaje que dice: "Program terminated normally". Naturalmente con un mensaje como éste no podemos estar seguros que el programa haya hecho la suma, pero existe una forma sencilla de verificarlo, utilizando el comando "r" del Debug podemos ver los contenidos de todos los registros del procesador, simplemente teclee:

- r [Enter]

Aparecera en pantalla cada registro con su respectivo valor actual:

AX=0006BX=0004CX=0000DX=0000SP=FFEEBP=0000SI=0000DI=0000

DS=0C1BES=0C1BSS=0C1BCS=0C1BIP=010A NV UP EI PL NZ NA PO NC

0C1B:010A 0F DB oF

Existe la posibilidad de que los registros contengan valores diferentes, pero AX y BX deben ser los mismos, ya que son los que acabamos de modificar.

Otra forma de ver los valores, mientras se ejecuta el programa es utilizando como parámetro para "g" la dirección donde queremos que termine la ejecución y muestre los valores de los registros, en este caso sería: g108, esta instrucción ejecuta el programa, se detiene en la dirección 108 y muestra los contenidos de los registros.

También se puede llevar un seguimiento de lo que pasa en los registros utilizando el comando "t" (trace), la función de este comando es ejecutar linea por linea lo que se ensambló mostrando cada vez los contenidos de los regitros.

Para salir del Debug se utiliza el comando "q" (quit).

309

Page 310: Programacion en Java

Guardar y cargar los programas

No sería práctico tener que digitar todo un programa cada vez que se necesite, para evitar eso es posible guardar un programa en el disco, con la enorme ventaja de que ya ensamblado no será necesario correr de nuevo debug para ejecutarlo.

Los pasos a seguir para guardar un programa ya almacenado en la memoria son:

Obtener la longitud del programa restando la dirección final de la dirección inicial, naturalmente en sistema hexadecimal.

Darle un nombre al programa y extensión

Poner la longitud del programa en el registro CX

Ordenar a Debug que escriba el programa en el disco.

Utilizando como ejemplo el programa del capítulo anterior tendremos una idea mas clara de como llevar estos pasos:

Al terminar de ensamblar el programa se vería así:

0C1B:0100 mov ax,0002

0C1B:0103 mov bx,0004

0C1B:0106 add ax,bx

0C1B:0108 int 20

0C1B:010A

- h 10a 100

020a 000a

- n prueba.com

- rcx

CX 0000

:000a

-w

Writing 000A bytes

Para obtener la longitud de un programa se utiliza el comando "h", el cual nos muestra la suma y resta de dos números en hexadecimal. Para obtener la longitud del nuestro le proporcionamos como parámetros el valor de la dirección final de nuestro programa (10A) y el valor de la dirección

310

Page 311: Programacion en Java

inicial (100). El primer resultado que nos muestra el comando es la suma de los parámetros y el segundo es la resta.

El comando "n" nos permite poner un nombre al programa.

El comando "rcx" nos permite cambiar el contenido del registro CX al valor que obtuvimos del tamaño del archivo con "h", en este caso 000a, ya que nos interesa el resultado de la resta de la dirección inicial a la dirección final.

Por último el comando w escribe nuestro programa en el disco, indicandonos cuantos bytes escribió.

Para cargar un archivo ya guardado son necesarios dos pasos:

Proporcionar el nombre del archivo que se cargará.

Cargarlo utilizando el comando "l" (load).

Para obtener el resultado correcto de los siguientes pasos es necesario que previamente se haya creado el programa anterior.

Dentro del Debug escribimos lo siguiente:

- n prueba.com

- l

- u 100 109

0C3D:0100 B80200 MOV AX,0002

0C3D:0103 BB0400 MOV BX,0004

0C3D:0106 01D8 ADD AX,BX

0C3D:0108 CD20 INT 20

El último comando, "u", se utiliza para verificar que el programa se cargó en memoria, lo que hace es desensamblar el código y mostrarlo ya desensamblado. Los parámetros le indican a Debug desde donde y hasta donde desensamblar.

Debug siempre carga los programas en memoria en la dirección 100H, a menos que se le indique alguna otra.

Condiciones, ciclos y bifurcaciones

Estas estructuras, o formas de control le dan a la máquina un cierto grado de desición basado en la información que recibe.

La forma mas sencilla de comprender este tema es por medio de ejemplos.

311

Page 312: Programacion en Java

Vamos a crear tres programas que hagan lo mismo: desplegar un número determinado de veces una cadena de caracteres en la pantalla.

- a100

0C1B:0100 jmp 125 ; brinca a la dirección 125H

0C1B:0102 [Enter]

- e 102 'Cadena a visualizar 15 veces' 0d 0a '$'

- a125

0C1B:0125 MOV CX,000F ; veces que se desplegara la cadena

0C1B:0128 MOV DX,0102 ; copia cadena al registro DX

0C1B:012B MOV AH,09 ; copia valor 09 al registro AH

0C1B:012D INT 21 ; despliega cadena

0C1B:012F LOOP 012D ; si CX>0 brinca a 012D

0C1B:0131 INT 20 ; termina el programa.

Por medio del comando "e" es posible introducir una cadena de caracteres en una determinada localidad de memoria, dada como parámetro, la cadena se introduce entre comillas, le sigue un espacio, luego el valor hexadecimal del retorno de carro, un espacio, el valor de linea nueva y por último el símbolo '$' que el ensamblador interpreta como final de la cadena. La interrupción 21 utiliza el valor almacenado en el registro AH para ejecutar una determinada función, en este caso mostrar la cadena en pantalla, la cadena que muestra es la que está almacenada en el registro DX. La instrucción LOOP decrementa automaticamente el registro CX en uno y si no ha llegado el valor de este registro a cero brinca a la casilla indicada como parámetro, lo cual crea un ciclo que se repite el número de veces especificado por el valor de CX. La interrupción 20 termina la ejecución del programa.

Otra forma de realizar la misma función pero sin utilizar el comando LOOP es la siguiente:

- a100

0C1B:0100 jmp 125 ; brinca a la dirección 125H

0C1B:0102 [Enter]

- e 102 'Cadena a visualizar 15 veces' 0d 0a '$'

- a125

0C1B:0125 MOV BX,000F ; veces que se desplegara la cadena

0C1B:0128 MOV DX,0102 ; copia cadena al registro DX

312

Page 313: Programacion en Java

0C1B:012B MOV AH,09 ; copia valor 09 al registro AH

0C1B:012D INT 21 ; despliega cadena

0C1B:012F DEC BX ; decrementa en 1 a BX

0C1B:0130 JNZ 012D ; si BX es diferente a 0 brinca a 012D

0C1B:0132 INT 20 ; termina el programa.

En este caso se utiliza el registro BX como contador para el programa, y por medio de la instrucción "DEC" se disminuye su valor en 1. La instrucción "JNZ" verifica si el valor de B es diferente a 0, esto con base en la bandera NZ, en caso afirmativo brinca hacia la dirección 012D. En caso contrario continúa la ejecución normal del programa y por lo tanto se termina.

Una útima variante del programa es utilizando de nuevo a CX como contador, pero en lugar de utilizar LOOP utilizaremos decrementos a CX y comparación de CX a 0.

- a100

0C1B:0100 jmp 125 ; brinca a la dirección 125H

0C1B:0102 [Enter]

- e 102 'Cadena a visualizar 15 veces' 0d 0a '$'

- a125

0C1B:0125 MOV DX,0102 ; copia cadena al registro DX

0C1B:0128 MOV CX,000F ; veces que se desplegara la cadena

0C1B:012B MOV AH,09 ; copia valor 09 al registro AH

0C1B:012D INT 21 ; despliega cadena

0C1B:012F DEC CX ; decrementa en 1 a CX

0C1B:0130 JCXZ 0134 ; si CX es igual a 0 brinca a 0134

0C1B:0132 JMP 012D ; brinca a la direcci&oaute;n 012D

0C1B:0134 INT 20 ; termina el programa

En este ejemplo se usó la instrucción JCXZ para controlar la condición de salto, el significado de tal función es: brinca si CX=0

El tipo de control a utilizar dependerá de las necesidades de programación en determinado momento.

Interrupciones

313

Page 314: Programacion en Java

Definición de interrupción:

Una interrupción es una instrucción que detiene la ejecución de un programa para permitir el uso de la UCP a un proceso prioritario. Una vez concluido este último proceso se devuelve el control a la aplicación anterior.

Por ejemplo, cuando estamos trabajando con un procesador de palabras y en ese momento llega un aviso de uno de los puertos de comunicaciones, se detiene temporalmente la aplicación que estabamos utilizando para permitir el uso del procesador al manejo de la información que está llegando en ese momento. Una vez terminada la transferencia de información se reanudan las funciones normales del procesador de palabras.

Las interrupciones ocurren muy seguido, sencillamente la interrupción que actualiza la hora del día ocurre aproximadamente 18 veces por segundo. Para lograr administrar todas estas interrupciones, la computadora cuenta con un espacio de memoria, llamado memoria baja, donde se almacenan las direcciones de cierta localidad de memoria donde se encuentran un juego de instrucciones que la UCP ejecutará para despues regresar a la aplicación en proceso.

En los programas anteriores hicimos uso de la interrupcion número 20H para terminar la ejecución de nuestros programas, ahora utilizaremos otra interrupción para mostrar información en pantalla:

Utilizando Debug tecleamos:

- a100

2C1B:0100 JMP 011D

2C1B:0102 [ENTER]

- E 102 'Hola, como estas.' 0D 0A '$'

- A011D

2C1B:011D MOV DX,0102

2C1B:0120 MOV AH,09

2C1B:0122 INT 21

2C1B:0123 INT 20

En este programa la interrupción 21H manda al monitor la cadena localizada en la dirección a la que apunta el registro DX.

El valor que se le da a AH determina cual de las opciones de la interrupción 21H sera utilizada, ya que esta interrupción cuenta con varias opciones.

El manejo directo de interrupciones es una de las partes mas fuertes del lenguaje ensamblador, ya que con ellas es posible controlar eficientemente todos los dispositivos internos y externos de una computadora gracias al completo control que se tiene sobre operaciones de entrada y salida.

Software necesario

314

Page 315: Programacion en Java

Para poder crear un programa se requieren varias herramientas:

Primero un editor para crear el programa fuente.

Segundo un compilador que no es mas que un programa que "traduce" el programa fuente a un programa objeto.

Y tercero un enlazador o linker, que genere el programa ejecutable a partir del programa objeto.

El editor puede ser cualquier editor de textos que se tenga a la mano, como compilador utilizaremos el MASM (macro ensamblador de Microsoft) ya que es el mas común, y como enlazador utilizaremos el programa link.

La extensión usada para que MASM reconozca los programas fuente en ensamblador es .ASM; una vez traducido el programa fuente, el MASM crea un archivo con la extensión .OBJ, este archivo contiene un "formato intermedio" del programa, llamado así porque aún no es ejecutable pero tampoco es ya un programa en lenguaje fuente. El enlazador genera, a partir de un archivo .OBJ o la combinación de varios de estos archivos, un programa executable, cuya extensión es usualmente .EXE aunque también puede ser .COM, dependiendo de la forma en que se ensambló.

Este tutorial describe la forma de trabajar con la versión 5.0 o posterior del MASM, la diferencia principal de esta versión con otras anteriores es la forma en que se declaran los segmentos de código, datos y la pila, pero la estructura de programación es la misma.

Utilización del MASM

Una vez que se creó el programa objeto se debe pasar al MASM para crear el código intermedio, el cual queda guardado en un archivo con extensión .OBJ. El comando para realizar esto es:

MASM nombre_archivo; [Enter]

Donde Nombre_Archivo es el nombre del programa fuente con extensión .ASM que se va a traducir. El punto y coma utilizados despues del nombre del archivo le indican al macro ensamblador que genere directamente el código intermedio, de omitirse este caracter el MASM pedirá el nombre del archivo a traducir, el nombre del archivo que se generará así como opciones de listado de información que puede proporcionar el traductor.

Es posible ejecutar el MASM utilizando parámetros para obtener un fin determinado, toda la lista de los mismos se encuentra en el manual del programa. Solo recordaré en este tutorial la forma de pasar dichos parámetros al MASM:

Todo parámetro va despues del simbolo "/". Es posible utilizar varios parámetros a la vez. Una vez tecleados todos los parámetros se escribe el nombre del archivo a ensamblar. Por ejemplo, si queremos que el MASM ensamble un programa llamado prueba, y ademas deseamos que despliege el número de lineas fuente y símbolos procesados (eso lo realiza con el parametro /v), y si ocurre un error que nos diga en que linea ocurrió (con el parametro /z), entonces tecleamos:

MASM /v /z prueba;

Uso del enlazador (linker)

El MASM unicamente puede crear programas en formato .OBJ, los cuales no son ejecutables por si solos, es necesario un enlazador que genere el código ejecutable.

315

Page 316: Programacion en Java

La utilización del enlazador es muy parecida a la del MASM, unicamente se teclea en el indicador del DOS:

LINK Nombre_Archivo ;

Donde Nombre_Archivo es el nombre del programa intermedio (OBJ). Esto generara directamente un archivo con el nombre del programa intermedio y la extensión .EXE

Formato interno de un programa

Para poder comunicarnos en cualquier lenguaje, incluyendo los lenguajes de programación, es necesario seguir un conjunto de reglas, de lo contrario no podríamos expresar lo que deseamos.

En este apartado veremos algunas de las reglas que debemos seguir para escribir un programa en lenguaje ensamblador, enfocandonos a la forma de escribir las instrucciones para que el ensamblador sea capaz de interpretarlas.

Basicamente el formato de una linea de código en lenguaje ensamblador consta de cuatro partes:

Etiqueta, variable o constante: No siempre es definida, si se define es necesario utilizar separadores para diferenciarla de las otras partes, usualmente espacios, o algún símbolo especial.

Directiva o instrucción: es el nombre con el que se conoce a la instrucción que queremos que se ejecute.

Operando(s): la mayoría de las instrucciones en ensamblador trabajan con dos operandos, aunque hay instrucciones que funcionan solo con uno. El primero normalmente es el operando destino, que es el depósito del resultado de alguna operación; y el segundo es el operando fuente, que lleva el dato que será procesado. Los operandos se separan uno del otro por medio de una coma ",".

Comentario: como su nombre lo indica es tan solo un escrito informativo, usado principalmente para explicar que está haciendo el programa en determinada linea; se separa de las otras partes por medio de un punto y coma ";". Esta parte no es necesaria en el programa, pero nos ayuda a depurar el programa en caso de errores o modificaciones.

Como ejemplo podemos ver una linea de un programa escrito en ensamblador:

Etiq1: MOV AX,001AH ; Inicializa AX con el valor 001A

Aquí tenemos la etiqueta "Etiq1" (Identificable como etiqueta por el símbolo final ":"), la instrucción "MOV", y los operandos "AX" como destino y "001A" como fuente, ademas del comentario que sigue despues del ";".

Un ejemplo de una declaración de una constante esta dado por:

UNO EQU 0001H

Donde "UNO" es el nombre de la constante que definimos, "EQU" es la directiva utilizada para usar a "UNO" como constante, y "0001H" es el operando, que en este caso sera el valor que guarde UNO.

Formato Externo de un programa

316

Page 317: Programacion en Java

Ademas de definir ciertas reglas para que el ensamblador pueda entender una instrucción es necesario darle cierta información de los recursos que se van a utilizar, como por ejemplo los segmentos de memoria que se van a utilizar, datos iniciales del programa y también donde inicia y donde termina nuestro código.

Un programa sencillo puede ser el siguiente:

.MODEL SMALL

.CODE

Programa:

MOV AX,4C00H

INT 21H

.STACK

END Programa

El programa realmente no hace nada, unicamente coloca el valor 4C00H en el registro AX, para que la interrupción 21H termine el programa, pero nos da una idea del formato externo en un programa de ensamblador.

La directiva .MODEL define el tipo de memoria que se utilizará; la directiva .CODE nos indica que lo que esta a continuación es nuestro programa; la etiqueta Programa indica al ensamblador el inicio del programa; la directiva .STACK le pide al ensamblador que reserve un espacio de memoria para las operaciones de la pila; la instrucción END Programa marca el final del programa.

Ejemplo práctico de un programa

Aquí se ejemplificará un programa que escriba una cadena en pantalla:

.MODEL SMALL

.CODE

Programa:

MOV AX, @DATA

MOV DS, AX

MOV DX, Offset Texto

MOV AH, 9

INT 21H

MOV AX,4C00H

317

Page 318: Programacion en Java

INT 21H

.DATA

Texto DB 'Mensaje en pantalla.$'

.STACK

END Programa

Los primeros pasos son iguales a los del programa anterior: se define el modelo de memoria, se indica donde inicia el código del programa y en donde comienzan las instrucciones.

A continuación se coloca @DATA en el registro AX para despues pasarlo al registro DS ya que no se puede copiar directamente una constante a un registro de segmento. El contenido de @DATA es el número del segmento que será utilizado para los datos. Luego se guarda en el registro DX un valor dado por "Offset Texto" que nos da la dirección donde se encuentra la cadena de caracteres en el segmento de datos. Luego utiliza la opción 9 (Dada por el valor de AH) de la interrupción 21H para desplegar la cadena posicionada en la dirección que contiene DX. Por último utiliza la opción 4CH de la interrupción 21H para terminar la ejecución del programa (aunque cargamos al registro AX el valor 4C00H la interrupción 21H solo toma como opción el contenido del registro AH).

La directiva .DATA le indica al ensamblador que lo que está escrito a continuación debe almacenarlo en el segmento de memoria destinado a los datos. La directiva DB es utilizada para Definir Bytes, ésto es, asignar a cierto identificador (en este caso "Texto") un valor, ya sea una constante o una cadena de caracteres, en este último caso deberá estar entre comillas sencillas ' y terminar con el simbolo "$".

Segmentos

La arquitectura de los procesadores x86 obliga al uso de segmentos de memoria para manejar la información, el tamaño de estos segmentos es de 64kb.

La razón de ser de estos segmentos es que, considerando que el tamaño máximo de un número que puede manejar el procesador esta dado por una palabra de 16 bits o registro, no sería posible accesar a más de 65536 localidades de memoria utilizando uno solo de estos registros, ahora, si se divide la memoria de la pc en grupos o segmentos, cada uno de 65536 localidades, y utilizamos una dirección en un registro exclusivo para localizar cada segmento, y entonces cada dirección de una casilla específica la formamos con dos registros, nos es posible accesar a una cantidad de 4294967296 bytes de memoria, lo cual es, en la actualidad, más memoria de la que veremos instalada en una PC.

Para que el ensamblador pueda manejar los datos es necesario que cada dato o instrucción se encuentren localizados en el área que corresponde a sus respectivos segmentos. El ensamblador accesa a esta información tomando en cuenta la localización del segmento, dada por los registros DS, ES, SS y CS, y dentro de dicho registro la dirección del dato específico. Es por ello que cuando creamos un programa empleando el Debug en cada linea que vamos ensamblando aparce algo parecido a lo siguiente:

1CB0:0102 MOV AX,BX

318

Page 319: Programacion en Java

En donde el primer número, 1CB0, corresponde al segmento de memoria que se está utilizando, el segundo se refiere la la dirección dentro de dicho segmento, y a continuación aparecen las instrucciones que se almacenaran a partir de esa dirección.

La forma de indicarle al ensamblador con cuales de los segmentos se va a trabajar es por medio de las directivas .CODE, .DATA y .STACK.

El ensamblador se encarga de ajustar el tamaño de los segmentos tomando como base el número de bytes que necesita cada instrucción que va ensamblando, ya que sería un desperdicio de memoria utilizar los segmentos completos. Por ejemplo, si un programa unicamente necesita 10kb para almacenar los datos, el segmento de datos unicamente sera de 10kb y no de los 64kb que puede manejar.

Tabla de símbolos

A cada una de las partes de una linea de código en ensamblador se le conoce como token, por ejemplo en la linea de código

MOV AX,Var

tenemos tres tokens, la instrucción MOV, el operando AX, y el operando VAR. El ensamblador lo que hace para generar el código OBJ es leer cada uno de los tokens y buscarlo en una tabla interna de "equivalencias" conocida como tabla de palabras reservadas, que es donde se encuentran todos los significados de los mnemónicos que utilizamos como instrucciones.

Siguiendo este proceso, el ensamblador lee MOV, lo busca en su tabla y al encontrarlo lo identifica como una instrucción del procesador, así mismo lee AX y lo reconoce como un registro del procesador, pero al momento de buscar el token Var en la tabla de palabras reservadas no lo encuentra y entonces lo busca en la tabla de símbolos que es una tabla donde se encuentran los nombres de las variables, constantes y etiquetas utilizadas en el programa donde se incluye su dirección en memoria y el tipo de datos que contiene.

Algunas veces el ensamblador se encuentra con algún token no definido en el programa, lo que hace en estos casos es dar una segunda pasada por el programa fuente para verificar todas las referencias a ese símbolo y colocarlo en la tabla de símbolos. Existen símbolos que no los va a encontrar ya que no pertenecen a ese segmento y el programa no sabe en que parte de la memoria se encontrara dicho segmento, en este momento entra en acción el enlazador, el cual crea la estructura que necesita el cargador para que el segmento y el token sean definidos cuando se cargue el programa y antes de que el mismo sea ejecutado.

Movimiento de datos

En todo programa es necesario mover datos en la memoria y en los registros de la UCP; existen diversas formas de hacer esto: puede copiar datos de la memoria a algún registro, de registro a registro, de un registro a una pila, de la pila a un registro, transmitir datos hacia dispositivos externos así como recibir datos de dichos dispositivos.

Este movimiento de datos está sujeto a reglas y restricciones. Algunas de ellas son las que se citan a continuación.

No es posible mover datos de una localidad de memoria a otra directamente, es necesario primero mover los datos de la localidad origen hacia un registro y luego del registro a la localidad destino.

319

Page 320: Programacion en Java

No se puede mover una constante directamente a un registro de segmentos, primero se debe mover a un registro de la UCP.

Es posible mover bloques de datos por medio de las instrucciones movs, que copia una cadena de bytes o palabras; movsb que copia n bytes de una localidad a otra; y movsw copia n palabras de una localidad a otra. Las dos últimas instrucciones toman los valores de las direcciones definidas por DS:SI como grupo de datos a mover y ES:DI como nueva localización de los datos.

Para mover los datos también existen las estructuras llamadas pilas, en este tipo de estructuras los datos se introducen con la instrucción push y se extraen con la instrucción pop

En una pila el primer dato introducido es el último que podemos sacar, esto es, si en nuestro programa utilizamos las instrucciones:

PUSH AX

PUSH BX

PUSH CX

Para devolver los valores correctos a cada registro al momento de sacarlos de la pila es necesario hacerlo en el siguiente orden:

POP CX

POP BX

POP AX

Para la comunicación con dispositivos externos se utilizan el comando out para mandar información a un puerto y el comando in para leer información recibida desde algun puerto.

La sintaxis del comando out es:

OUT DX,AX

Donde DX contiene el valor del puerto que se utilizará para la comunicación y AX contiene la información que se mandará.

La sintaxis del comando in es:

IN AX,DX

Donde AX es el registro donde se guardará la información que llegue y DX contiene la dirección del puerto por donde llegará la información

Operaciones lógicas y aritméticas

Las instrucciones de las operaciones lógicas son: and, not, or y xor, éstas trabajan sobre los bits de sus operandos.

Para verificar el resultado de operaciones recurrimos a las instrucciones cmp y test.

320

Page 321: Programacion en Java

Las instrucciones utilizadas para las operaciones algebraicas son: para sumar add, para restar sub, para multiplicar mul y para dividir div.

Casi todas las instrucciones de comparación están basadas en la información contenida en el registro de banderas. Normalmente las banderas de este registro que pueden ser directamente manipuladas por el programador son la bandera de dirección de datos DF, usada para definir las operaciones sobre cadenas. Otra que también puede ser manipulada es la bandera IF por medio de las instrucciones sti y cli, para activar y desactivar respectivamente las interrupciones.

Saltos, ciclos y procedimientos

Los saltos incondicionales en un programa escrito en lenguaje ensamblador están dados por la instrucción jmp, un salto es alterar el flujo de la ejecución de un programa enviando el control a la dirección indicada.

Un ciclo, conocido también como iteración, es la repetición de un proceso un cierto número de veces hasta que alguna condición se cumpla. En estos ciclos se utilizan los brincos "condicionales" basados en el estado de las banderas. Por ejemplo la instrucción jnz que salta solamente si el resultado de una operación es diferente de cero y la instrucción jz que salta si el resultado de la operación es cero.

Por último tenemos los procedimientos o rutinas, que son una serie de pasos que se usarán repetidamente en el programa y en lugar de escribir todo el conjunto de pasos unicamente se les llama por medio de la instrucción call.

Un procedimiento en ensamblador es aquel que inicie con la palabra Proc y termine con la palabra ret.

Realmente lo que sucede con el uso de la instrucción call es que se guarda en la pila el registro IP y se carga la dirección del procedimiento en el mismo registro, conociendo que IP contiene la localización de la siguiente instrucción que ejecutara la UCP, entonces podemos darnos cuenta que se desvía el flujo del programa hacia la dirección especificada en este registro. Al momento en que se llega a la palabra ret se saca de la pila el valor de IP con lo que se devuelve el control al punto del programa donde se invocó al procedimiento.

Es posible llamar a un procedimiento que se encuentre ubicado en otro segmento, para ésto el contenido de CS (que nos indica que segmento se está utilizando) es empujado también en la pila.

Instrucción MOV

Propósito: Transferencia de datos entre celdas de memoria, registros y acumulador.

Sintaxis:

MOV Destino,Fuente

Donde Destino es el lugar a donde se moverán los datos y fuente es el lugar donde se encuentran dichos datos.

Los diferentes movimientos de datos permitidos para esta instrucción son:

Destino: memoria. Fuente: acumulador

321

Page 322: Programacion en Java

Destino: acumulador. Fuente: memoria

Destino: registro de segmento. Fuente: memoria/registro

Destino: memoria/registro. Fuente: registro de segmento

Destino: registro. Fuente: registro

Destino: registro. Fuente: memoria

Destino: memoria. Fuente: registro

Destino: registro. Fuente: dato inmediato

Destino: memoria. Fuente: dato inmediato

Ejemplo:

MOV AX,0006h

MOV BX,AX

MOV AX,4C00h

INT 21H

Este pequeño programa mueve el valor 0006H al registro AX, luego mueve el contenido de AX (0006h) al registro BX, por último mueve el valor 4C00h al registro AX para terminar la ejecución con la opción 4C de la interrupción 21h.

Instrucción MOVS (MOVSB) (MOVSW)

Propósito: Mover cadenas de bytes o palabras desde la fuente, direccionada por SI, hasta el destino direccionado por DI.

Sintaxis:

MOVS

Este comando no necesita parametros ya que toma como dirección fuente el contenido del registro SI y como destino el contenido de DI. La secuencia de instrucciones siguiente ilustran esto:

MOV SI, OFFSET VAR1

MOV DI, OFFSET VAR2

MOVS

Primero inicializamos los valores de SI y DI con las direcciones de las variables VAR1 y VAR2 respectivamente, despues al ejecutar MOVS se copia el contenido de VAR1 a VAR2.

322

Page 323: Programacion en Java

Los comandos MOVSB y MOVSW se utilizan de la misma forma que MOVS, el primero mueve un byte y el segundo una palabra.

Instrucción LODS (LODSB) (LODSW)

Propósito: Cargar cadenas de un byte o palabra al acumulador.

Sintaxis:

LODS

Esta instrucción toma la cadena que se encuentre en la dirección especificada por SI, la carga al registro AL (o AX) y suma o resta 1 (segun el estado de DF) a SI si la transferencia es de bytes o 2 si la transferencia es de palabras.

MOV SI, OFFSET VAR1

LODS

La primer linea carga la dirección de VAR1 en SI y la segunda linea lleva el contenido de esa localidad al registro AL.

Los comandos LODSB y LODSW se utilizan de la misma forma, el primero carga un byte y el segundo una palabra (utiliza el registro completo AX).

Instrucción LAHF

Propósito: Transfiere al registro AH el contenido de las banderas

Sintaxis:

LAHF

Esta instrucción es útil para verificar el estado de las banderas durante la ejecución de nuestro programa.

Las banderas quedan en el siguiente orden dentro del registro:

SF ZF ¿? AF ¿? PF ¿? CF

El simbolo "¿?" significa que en esos bits habrá. un valor indefinido.

Instrucción LDS

Propósito: Cargar el registro del segmento de datos

Sintaxis:

LDS destino, fuente

323

Page 324: Programacion en Java

El operando fuente debe ser una palabra doble en memoria. La palabra asociada con la dirección mas grande es transferida a DS, o sea que se toma como la dirección del segmento. La palabra asociada con la dirección menor es la dirección del desplazamiento y se deposita en el registro señalado como destino.

Instrucción LEA

Propósito: Carga la dirección del operando fuente.

Sintaxis:

LEA destino, fuente

El operando fuente debe estar ubicado en memoria, y se coloca su desplazamiento en el registro índice o apuntador especificado en destino.

Para ilustrar una de las facilidades que tenemos con este comando pongamos una equivalencia:

MOV SI, OFFSET VAR1

Equivale a:

LEA SI, VAR1

Es muy probable que para el programador sea mas sencillo crear programas extensos utilizando este último formato.

Instrucción LES

Propósito: Carga el registro del segmento extra

Sintaxis:

LES destino, fuente

El operando fuente debe ser un operando en memoria de palabra doble. El contenido de la palabra con la dirección mayor se interpreta como la dirección del segmento y se coloca en ES. La palabra con la dirección menor es la dirección del desplazamiento y se coloca en el registro especificado en el parámetro destino.

Instrucción POP

Propósito: Recupera un dato de la pila

Sintaxis:

POP destino

Esta instrucción transfiere el último valor almacenado en la pila al operando destino, después incrementa en dos el registro SP.

324

Page 325: Programacion en Java

Este incremento se debe a que la pila va creciendo desde la dirección mas alta de memoria del segmento hacia la mas baja, y la pila solo trabaja con palabras (2 bytes), entonces al incrementar en dos el registro SP realmente se le esta restando dos al tamaño real de la pila.

Instrucción POPF

Propósito: Extrae las banderas almacenadas en la pila.

Sintaxis:

POPF

Este comando transfiere bits de la palabra almacenada en la parte superior de la pila hacia el registro de banderas.

La forma de transferencia es la siguiente:

BIT BANDERA

0 CF

2 PF

4 AF

6 ZF

7 SF

8 TF

9 IF

10 DF

11 OF

Estas localizaciones son las mismas para el comando PUSHF

Una vez hecha la transferencia se incrementa en 2 el registro SP disminuyendo así el tamaño de la pila.

Instrucción PUSH

Propósito: Coloca una palabra en la pila.

Sintaxis:

PUSH fuente

325

Page 326: Programacion en Java

La instrucción PUSH decrementa en dos el valor de SP y luego transfiere el contenido del operando fuente a la nueva dirección resultante en el registro recién modificado.

El decremento en la dirección se debe a que al agregar valores a la pila ésta crece de la dirección mayor a la dirección menor del segmento, por lo tanto al restarle 2 al valor del registro SP lo que hacemos es aumentar el tamaño de la pila en dos bytes, que es la única cantidad de información que puede manejar la pila en cada entrada y salida de datos.

Instrucción PUSHF

Propósito: Coloca el valor de las banderas en la pila

Sintaxis:

PUSHF

Este comando decrementa en 2 el valor del registro SP y luego se transfiere el contenido del registro de banderas a la pila, en la dirección indicada por SP.

Las banderas quedan almacenadas en memoria en los mismos bits indicados en el comando POPF

Instrucciones lógicas.

Son utilizadas para realizar operaciones lógicas sobre los operandos.

AND

NEG

NOT

OR

TEST

XOR

Instrucciones aritméticas.

Se usan para realizar operaciones aritméticas sobre los operandos.

ADC

ADD

DIV

IDIV

MUL

326

Page 327: Programacion en Java

IMUL

SBB

SUB

Instrucción AND

Propósito: Realiza la conjunción de los operandos bit por bit.

Sintaxis:

AND destino, fuente

Con esta instrucción se lleva a cabo la operación "y" lógica de los dos operandos:

Fuente Destino | Destino

--------------------------

1 1 | 1

1 0 | 0

0 1 | 0

0 0 | 0

El resultado de la operación se almacena en el operando destino.

Instrucción NEG

Propósito: Genera el complemento a 2

Sintaxis:

NEG destino

Esta instrucción genera el complemento a 2 del operando destino y lo almacena en este mismo operando. Por ejemplo, si AX guarda el valor de 1234H, entonces:

NEG AX

Nos dejaría almacenado en el registro AX el valor EDCCH.

Instrucción NOT

Propósito: Lleva a cabo la negación bit por bit del operando destino.

Sintaxis:

327

Page 328: Programacion en Java

NOT destino

El resultado se guarda en el mismo operando destino.

Instrucción OR

Propósito: OR inclusivo lógico

Sintaxis:

OR destino, fuente

La instrucción OR lleva a cabo, bit por bit, la disyunción inclusiva lógica de los dos operandos:

Fuente Destino | Destino

--------------------------

1 1 | 1

1 0 | 1

0 1 | 1

0 0 | 0

Instrucción TEST

Propósito: Comparar logicamente los operandos

Sintaxis:

TEST destino, fuente

Realiza una conjunción, bit por bit, de los operandos, pero a diferencia de AND esta instrucción no coloca el resultado en el operando destino, solo tiene efecto sobre el estado de las banderas.

Instrucción XOR

Propósito: OR exclusivo

Sintaxis:

XOR destino, fuente

Su función es efectuar bit por bit la disyunción exclusiva lógica de los dos operandos.

Fuente Destino | Destino

--------------------------

328

Page 329: Programacion en Java

1 1 | 0

0 0 | 1

0 1 | 1

0 0 | 0

Instrucción ADC

Propósito: Adición con acarreo.

Sintaxis:

ADC destino, fuente

Lleva a cabo la suma de dos operandos y suma uno al resultado en caso de que la bandera CF esté activada, esto es, en caso de que exista acarreo.

El resultado se guarda en el operando destino.

Instrucción ADD

Propósito: Adición de los operandos.

Sintaxis:

ADD destino, fuente

Suma los dos operandos y guarda el resultado en el operando destino.

Instrucción DIV

Propósito: División sin signo

Sintaxis:

DIV fuente

El divisor puede ser un byte o palabra y es el operando que se le da a la instrucción.

Si el divisor es de 8 bits se toma como dividendo el registro de 16 bits AX y si el divisor es de 16 bits se tomara como dividendo el registro par DX:AX, tomando como palabra alta DX y como baja AX.

Si el divisor fué un byte el cociente se almacena en el registro AL y el residuo en AH, si fué una palabra el cociente se guarda en AX y el residuo en DX.

Instrucción IDIV

Propósito: División con signo

329

Page 330: Programacion en Java

Sintaxis:

IDIV fuente

Consiste basicamente en lo mismo que la instrucción DIV, solo que esta última realiza la operación con signo.

Para sus resultados utiliza los mismos registros que la instrucción DIV.

Instrucción MUL

Propósito: Multiplicación sin signo

Sintaxis:

MUL fuente

El ensamblador asume que el multiplicando sera del mismo tamaño que el del multiplicador, por lo tanto multiplica el valor almacenado en el registro que se le da como operando por el que se encuentre contenido en AH si el multiplicador es de 8 bits o por AX si el multiplicador es de 16 bits.

Cuando se realiza una multiplicación con valores de 8 bits el resultado se almacena en el registro AX y cuando la multiplicación es con valores de 16 bits el resultado se almacena en el registro par DX:AX.

Instrucción IMUL

Propósito: Multiplicación de dos enteros con signo.

Sintaxis:

IMUL fuente

Este comando hace lo mismo que el anterior, solo que si toma en cuenta los signos de las cantidades que se multiplican.

Los resultados se guardan en los mismos registros que en la instrucción MUL.

Instrucción SBB

Propósito: Substracción con acarreo

Sintaxis:

SBB destino, fuente

Esta instrucción resta los operandos y resta uno al resultado si CF está activada. El operando fuente siempre se resta del destino.

Este tipo de substracción se utiliza cuando se trabaja con cantidades de 32 bits.

330

Page 331: Programacion en Java

Instrucción SUB

Propósito: Substracción

Sintaxis:

SUB destino, fuente

Resta el operando fuente del destino.

Instrucciones de salto

Son utilizadas para transferir el flujo del proceso al operando indicado.

JMP

JA (JNBE)

JAE (JNBE)

JB (JNAE)

JBE (JNA)

JE (JZ)

JNE (JNZ)

JG (JNLE)

JGE (JNL)

JL (JNGE)

JLE (JNG)

JC

JNC

JNO

JNP (JPO)

JNS

JO

JP (JPE)

JS

331

Page 332: Programacion en Java

Instrucciones para ciclos:

LOOP Transfieren el flujo del proceso, condicional o incondicionalmente, a un destino repitiendose esta acción hasta que el contador sea cero.

LOOP

LOOPE

LOOPNE

Instrucciones de conteo

Se utilizan para decrementar o incrementar el contenido de los contadores.

DEC

INC

Instrucciones de comparación

Son usadas para comparar operandos, afectan al contenido de las banderas.

CMP

CMPS (CMPSB) (CMPSW)

Instrucciones de banderas

Afectan directamente al contenido de las banderas.

CLC

CLD

CLI

CMC

STC

STD

STI

Instrucción JMP

Propósito: Salto incondicional

Sintaxis:

332

Page 333: Programacion en Java

JMP destino

Esta instrucción se utiliza para desviar el flujo de un programa sin tomar en cuenta las condiciones actuales de las banderas ni de los datos.

Instrucción JA (JNBE)

Propósito: Brinco condicional

Sintaxis:

JA Etiqueta

Después de una comparación este comando salta si está arriba o salta si no está abajo o si no es igual.

Esto significa que el salto se realiza solo si la bandera CF esta desactivada o si la bandera ZF esta desactivada (que alguna de las dos sea igual a cero).

Instrucción JAE (JNB)

Propósito: salto condicional

Sintaxis:

JAE etiqueta

Salta si está arriba o si es igual o salta si no está abajo.

El salto se efectua si CF esta desactivada.

Instrucción JB (JNAE)

Propósito: salto condicional

Sintaxis:

JB etiqueta

Salta si está abajo o salta si no está arriba o si no es igual.

Se efectúa el salto si CF esta activada.

Instrucción JBE (JNA)

Propósito: salto condicional

Sintaxis:

JBE etiqueta

333

Page 334: Programacion en Java

Salta si está abajo o si es igual o salta si no está arriba.

El salto se efectúa si CF está activado o si ZF está activado (que cualquiera sea igual a 1).

Instrucción JE (JZ)

Propósito: salto condicional

Sintaxis:

JE etiqueta

Salta si es igual o salta si es cero.

El salto se realiza si ZF está activada.

Instrucción JNE (JNZ)

Propósito: salto condicional

Sintaxis:

JNE etiqueta

Salta si no es igual o salta si no es cero.

El salto se efectua si ZF está desactivada.

Instrucción JG (JNLE)

Propósito: salto condicional, se toma en cuenta el signo.

Sintaxis:

JG etiqueta

Salta si es más grande o salta si no es menor o igual.

El salto ocurre si ZF = 0 u OF = SF.

Instrucción JGE (JNL)

Propósito: salto condicional, se toma en cuenta el signo.

Sintaxis:

JGE etiqueta

Salta si es más grande o igual o salta si no es menor que.

334

Page 335: Programacion en Java

El salto se realiza si SF = OF

Instrucción JL (JNGE)

Propósito: salto condicional, se toma en cuenta el signo.

Sintaxis:

JL etiqueta

Salta si es menor que o salta si no es mayor o igual.

El salto se efectúa si SF es diferente a OF.

Instrucción JLE (JNG)

Propósito: salto condicional, se toma en cuenta el signo.

Sintaxis:

JLE etiqueta

Salta si es menor o igual o salta si no es más grande.

El salto se realiza si ZF = 1 o si SF es diferente a OF

Instrucción JC

Propósito: salto condicional, se toman en cuenta las banderas.

Sintaxis:

JC etiqueta

Salta si hay acarreo.

El salto se realiza si CF = 1

Instrucción JNC

Propósito: salto condicional, se toma en cuenta el estado de las banderas.

Sintaxis:

JNC etiqueta

Salta si no hay acarreo.

El salto se efectúa si CF = 0.

335

Page 336: Programacion en Java

Instrucción JNO

Propósito: salto condicional, se toma en cuenta el estado de las banderas.

Sintaxis:

JNO etiqueta

Salta si no hay desbordamiento.

El salto se efectua si OF = 0.

Instrucción JNP (JPO)

Propósito: salto condicional, toma en cuenta el estado de las banderas.

Sintaxis:

JNP etiqueta

Salta si no hay paridad o salta si la paridad es non.

El salto ocurre si PF = 0.

Instrucción JNS

Propósito: salto condicional, toma en cuenta el estado de las banderas.

Sintaxis:

JNP etiqueta

Salta si el signo esta desactivado.

El salto se efectúa si SF = 0.

Instrucción JO

Propósito: salto condicional, toma en cuenta el estado de las banderas.

Sintaxis:

JO etiqueta

Salta si hay desbordamiento (overflow).

El salto se realiza si OF = 1.

Instrucción JP (JPE)

336

Page 337: Programacion en Java

Propósito: salto condicional, toma en cuenta el estado de las banderas.

Sintaxis:

JP etiqueta

Salta si hay paridad o salta si la paridad es par.

El salto se efectúa si PF = 1.

Instrucción JS

Propósito: salto condicional, toma en cuenta el estado de las banderas.

Sintaxis:

JS etiqueta

Salta si el signo está prendido.

El salto se efectúa si SF = 1.

Instrucción LOOP

Propósito: Generar un ciclo en el programa.

Sintaxis:

LOOP etiqueta

La instrucción loop decrementa CX en 1, y transfiere el flujo del programa a la etiqueta dada como operando si CX es diferente a 1.

Instrucción LOOPE

Propósito: Generar un ciclo en el programa considerando el estado de ZF

Sintaxis:

LOOPE etiqueta

Esta instrucción decrementa CX en 1. Si CX es diferente a cero y ZF es igual a 1, entonces el flujo del programa se transfiere a la etiqueta indicada como operando.

Instrucción LOOPNE

Propósito: Generar un ciclo en el programa, considerando el estado de ZF

Sintaxis:

LOOPNE etiqueta

337

Page 338: Programacion en Java

Esta instrucción decrementa en uno a CX y transfiere el flujo del programa solo si ZF es diferente a 0.

Instrucción DEC

Propósito: Decrementar el operando

Sintaxis:

DEC destino

Esta operación resta 1 al operando destino y almacena el nuevo valor en el mismo oeprando.

Instrucción INC

Propósito: Incrementar el operando.

Sintaxis:

INC destino

La instrucción suma 1 al operando destino y guarda el resultado en el mismo operando destino.

Instrucción CMP

Propósito: Comparar los operandos.

Sintaxis:

CMP destino, fuente

Esta instrucción resta el operando fuente al operando destino pero sin que éste almacene el resultado de la operación, solo se afecta el estado de las banderas.

Instrucción CMPS (CMPSB) (CMPSW)

Propósito: Comparar cadenas de un byte o palabra.

Sintaxis:

CMP destino, fuente

Con esta instrucción la cadena de caracteres fuente se resta de la cadena destino.

Se utilizan DI como indice para el segmento extra de la cadena fuente y SI como indice de la cadena destino.

Solo se afecta el contenido de las banderas y tanto DI como SI se incrementan.

Instrucción CLC

338

Page 339: Programacion en Java

Propósito: Limpiar bandera de acarreo.

Sintaxis:

CLC

Esta instrucción apaga el bit correspondiente a la bandera de acarreo, o sea, lo pone en cero.

Instrucción CLD

Propósito: Limpiar bandera de dirección

Sintaxis:

CLD

La instrucción CLD pone en cero el bit correspondiente a la bandera de dirección.

Instrucción CLI

Propósito: Limpiar bandera de interrupción

Sintaxis:

CLI

CLI pone en cero la bandera de interrupciones, desabilitando así aquellas interrupciones enmascarables.

Una interrupción enmascarable es aquella cuyas funciones son desactivadas cuando IF = 0.

Instrucción CMC

Propósito: Complementar la bandera de acarreo.

Sintaxis:

CMC

Esta instrucción complementa el estado de la bandera CF, si CF = 0 la instrucción la iguala a 1, y si es 1 la instrucción la iguala a 0.

Podemos decir que únicamente "invierte" el valor de la bandera.

Instrucción STC

Propósito: Activar la bandera de acarreo.

Sintaxis:

STC

339

Page 340: Programacion en Java

Esta instrucción pone la bandera CF en 1.

Instrucción STD

Propósito: Activar la bandera de dirección.

Sintaxis:

STD

La instrucción STD pone la bandera DF en 1.

Instrucción STI

Propósito: Acticar la bandera de interrupción.

Sintaxis:

STI

La instrucción activa la bandera IF, esto habilita las interrupciones externas enmascarables (las que funcionan unicamente cuando IF = 1 ).

Interrupciones internas de hardware

Las interrupciones internas son generadas por ciertos eventos que surgen durante la ejecución de un programa.

Este tipo de interrupciones son manejadas en su totalidad por el hardware y no es posible modificarlas.

Un ejemplo claro de este tipo de interrupciones es la que actualiza el contador del reloj interno de la computadora, el hardware hace el llamado a esta interrupción varias veces durante un segundo para mantener la hora actualizada.

Aunque no podemos manejar directamente esta interrupción (no podemos controlar por software las actualizaciones del reloj), es posible utilizar sus efectos en la computadora para nuestro beneficio, por ejemplo para crear un "reloj virtual" actualizado continuamente gracias al contador del reloj interno. Únicamente debemos escribir un programa que lea el valor actual del contador y lo traduzca a un formato entendible para el usuario.

Interrupciones externas de hardware

Las interrupciones externas las generan los dispositivos periféricos, como pueden ser: teclado, impresoras, tarjetas de comunicaciones, etc. También son generadas por los coprocesadores.

No es posible desactivar a las interrupciones externas.

Estas interrupciones no son enviadas directamente a la UCP, sino que se mandan a un circuito integrado cuya función es exclusivamente manejar este tipo de interrupciones. El circuito, llamado PIC 8259A, si es controlado por la UCP utilizando para tal control una serie de vias de comunicación llamadas puertos.

340

Page 341: Programacion en Java

Interrupciones de software

Las interrupciones de software pueden ser activadas directamente por el ensamblador invocando al número de interrupción deseada con la instrucción INT.

El uso de las interrupciones nos ayuda en la creación de programas, utilizandolas nuestros programas son más cortos, es más fácil entenderlos y usualmente tienen un mejor desempeño debido en gran parte a su menor tamaño.

Este tipo de interrupciones podemos separarlas en dos categorias: las interrupciones del sistema operativo DOS y las interrupciones del BIOS.

La diferencia entre ambas es que las interrupciones del sistema operativo son más fáciles de usar pero también son más lentas ya que estas interrupciones hacen uso del BIOS para lograr su cometido, en cambio las interrupciones del BIOS son mucho más rápidas pero tienen la desventaja que, como son parte del hardware son muy específicas y pueden variar dependiendo incluso de la marca del fabricante del circuito.

La elección del tipo de interrupción a utilizar dependerá unicamente de las caracteristicas que le quiera dar a su programa: velocidad (utilizando las del BIOS) o portabilidad (utilizando las del DOS).

Interrupción 21H

Propósito: Llamar a diversas funciones del DOS.

Sintaxis:

Int 21H

Nota: Cuando trabajamos en MASM es necesario especificar que el valor que estamos utilizando es hexadecimal.

Esta interrupción tiene varias funciones, para accesar a cada una de ellas es necesario que el el registro AH se encuentre el número de función que se requiera al momento de llamar a la interrupción.

Funciones para desplegar información al video.

02H Exhibe salida

09H Impresión de cadena (video)

40H Escritura en dispositivo/Archivo

Funciones para leer información del teclado.

01H Entrada desde teclado

0AH Entrada desde teclado usando buffer

3FH Lectura desde dispositivo/archivo

341

Page 342: Programacion en Java

Funciones para trabajar con archivos.

En esta sección unicamente se expone la tarea específica de cada función, para una referencia acerca de los conceptos empleados refierase a la unidad 7, titulada: "Introducción al manejo de archivos".

Método FCB

0FH Abrir archivo

14H Lectura secuencial

15H Escritura secuencial

16H Crear archivo

21H Lectura aleatoria

22H Escritura aleatoria

Handles

3CH Crear archivo

3DH Abrir archivo

3EH Cierra manejador de archivo

3FH Lectura desde archivo/dispositivo

40H Escritura en archivo/dispositivo

42H Mover apuntador de lectura/escritura en archivo

Función 02H

Uso:

Despliega un caracter a la pantalla.

Registros de llamada:

AH = 02H

DL = Valor del caracter a desplegar.

Registros de retorno:

Ninguno

342

Page 343: Programacion en Java

Esta función nos despliega el caracter cuyo codigo hexagesimal corresponde al valor almacenado en el registro DL, no se modifica ningún registro al utilizar este comando.

Es recomendado el uso de la función 40H de la misma interrupción en lugar de esta función.

Función 09H

Uso:

Despliega una cadena de carateres en la pantalla.

Registros de llamada:

AH = 09H

DS:DX = Dirección de inicio de una cadena de caracteres

Registros de retorno:

Ninguno.

Esta función despliega los caracteres, uno a uno, desde la dirección indicada en el registro DS:DX hasta encontrar un caracter $, que es interpretado como el final de la cadena.

Se recomienda utilizar la función 40H en lugar de esta función.

Función 40H

Uso:

Escribir a un dispositivo o a un archivo.

Registros de llamada:

AH = 40H

BX = Vía de comunicación

CX = Cantidad de bytes a escribir

DS:DX = Dirección del inicio de los datos a escribir

Registros de retorno:

CF = 0 si no hubo error

AX = Número de bytes escritos

CF = 1 si hubo error

AX = Código de error

343

Page 344: Programacion en Java

El uso de esta función para desplegar información en pantalla se realiza dandole al registro BX el valor de 1 que es el valor preasignado al video por el sistema operativo MS-DOS.

Función 01H

Uso:

Leer un caracter del teclado y desplegarlo.

Registros de llamada:

AH = 01H

Registros de retorno:

AL = Caracter leído

Con esta función es muy sencillo leer un caracter del teclado, el código hexadecimal del caracter leído se guarda en el registro AL. En caso de que sea un caracter extendido el registro AL contendra el valor de 0 y será necesario llamar de nuevo a la función para obtener el código de este caracter.

Función 0AH

Uso:

Leer caracteres del teclado y almacenarlos en un buffer.

Registros de llamada:

AH = 0AH

DS:DX = Dirección del área de almacenamiento

BYTE 0 = Cantidad de bytes en el área

BYTE 1 = Cantidad de bytes leídos

desde BYTE 2 hasta BYTE 0 + 2 = caracteres leídos

Registros de retorno:

Ninguno

Los caracteres son leídos y almacenados en un espacio predefinido de memoria. La estructura de este espacio le indica que en el primer byte del mismo se indican cuantos caracteres serán leídos. En el segundo byte se almacena el número de caracteres que ya se leyeron, y del tercer byte en adelante se escriben los caracteres leídos.

Cuando se han almacenado todos los caracteres indicados menos uno la bocina suena y cualquier caracter adicional es ignorado. Para terminar la captura de la cadena es necesario darle [ENTER].

344

Page 345: Programacion en Java

Función 3FH

Uso:

Leer información de un dispositivo o archivo.

Registros de llamada:

AH = 3FH

BX = Número asignado al dispositivo

CX = Número de bytes a procesar

DS:DX = Dirección del área de almacenamiento

Registros de retorno:

CF = 0 si no hay error y AX = número de bytes leidos.

CF = 1 si hay error y AX contendra el código del error.

Función 0FH

Uso:

Abrir archivo FCB

Registros de llamada:

AH = 0FH

DS:DX = Apuntador a un FCB

Registros de retorno:

AL = 00H si no hubo problema, de lo contrario regresa 0FFH

Función 14H

Uso:

Leer secuencialmente un archivo FCB.

Registros de llamada:

AH = 14H

DS:DX = Apuntador a un FCB ya abierto.

345

Page 346: Programacion en Java

Registros de retorno:

AL = 0 si no hubo errores, de lo contrario se regresara el código correspondiente de error: 1 error al final del archivo, 2 error en la estructura del FCB y 3 error de lectura parcial.

Esta función lo que hace es que lee el siguiente bloque de información a partir de la dirección dada por DS:DX, y actualiza este registro.

Función 15H

Uso:

Escribir secuencialmente a un archivo FCB

Registros de llamada:

AH = 15H

DS:DX = Apuntador a un FCB ya abierto

Registros de retorno:

AL = 00H si no hubo errores, de lo contrario contendra el código del error: 1 disco lleno o archivo de solo lectura, 2 error en la formación o especificación del FCB.

La función 15H después de escribir el registro al bloque actual actualiza el FCB.

Función 16H

Uso:

Crear un archivo FCB.

Registros de llamada:

AH = 16H

DS:DX = Apuntador a un FCB ya abierto.

Registros de retorno:

AL = 00H si no hubo errores, de lo contrario contendra el valor 0FFH

Se basa en la información proveida en un FCB para crear un archivo en el disco.

Función 21H

Uso:

Leer en forma aleatoria un archivo FCB.

346

Page 347: Programacion en Java

Registros de llamada:

AH = 21H

DS:DX = Apuntador a un FCB ya abierto.

Registros de retorno:

A = 00H si no hubo error, de lo contrario AH contendra el código del error: 1 si es fin de archivo, 2 si existe error de especificación de FCB y 3 si se leyó un registro parcial o el apuntador del archivo se encuentra al final del mismo.

Esta función lee el registro especificado por los campos del bloque actual y registro actual de un FCB abierto y coloca la información en el DTA (área de transferencia de disco o Disk Transfer Area).

Función 22H

Uso:

Escribir en forma aleatoria en un archivo FCB.

Registros de llamada:

AH = 22H

DS:DX = Apuntador a un FCB abierto.

Registros de retorno:

AL = 00H si no hubo error, de lo contrario contendrá el código del error: 1 si el disco está lleno o es archivo de solo lectura y 2 si hay error en la especificación de FCB.

Escribe el registro especificado por los campos del bloque actual y registro actual de un FCB abierto. Escribe dicha información a partir del contenido del DTA (área de transferencia de disco).

Función 3CH

Uso:

Crear un archivo si no existe o dejarlo en longitud 0 si existe. (Handle)

Registros de llamada:

AH = 3CH

CH = Atributo de archivo

DS:DX = Apuntador a una especificaión ASCIIZ

Registros de retorno:

347

Page 348: Programacion en Java

CF = 0 y AX el número asignado al handle si no hay error, en caso de haberlo CF será 1 y AX contendra el código de error: 3 ruta no encontrada, 4 no hay handles disponibles para asignar y 5 acceso negado.

Esta función sustituye a la 16H. El nombre del archivo es especificado en una cadena ASCIIZ, la cual tiene como característica la de ser una cadena de bytes convencional terminada con un caracter 0.

El archivo creado contendra los atributos definidos en el registro CX en la siguiente forma:

Valor Atributos

00H Normal

02H Escondido

04H Sistema

06H Escondido y de sistema

El archivo se crea con los permisos de lectura y escritura. No es posible crear directorios utilizando esta función.

Función 3DH

Uso:

Abre un archivo y regrese un handle

Registros de llamada:

AH = 3DH

AL = modo de acceso

DS:DX = Apuntador a una especificación ASCIIZ

Registros de retorno:

CF = 0 y AX = número de handle si no hay errores, de lo contrario CF = 1 y AX = código de error: 01H si no es válida la función, 02H si no se encontró el archivo, 03H si no se encontr´o la ruta, 04H si no hay handles disponibles, 05H en caso de acceso negado, y 0CH si el código de acceso no es válido.

El handle regresado es de 16 bits.

El código de acceso se especifica en la siguiente forma:

BITS

7 6 5 4 3 2 1

348

Page 349: Programacion en Java

. . . . 0 0 0 Solo lectura

. . . . 0 0 1 Solo escritura

. . . . 0 1 0 Lectura/Escritura

. . . X . . . RESERVADO

Función 3EH

Uso:

Cerrar archivo (Handle).

Registros de llamada:

AH = 3EH

BX = Handle asignado

Registros de retorno:

CF = 0 si no hubo errores, en caso contrario CF será 1 y AX contendrá el código de error: 06H si el handle es inválido.

Esta función actualiza el archivo y libera o deja disponible el handle que estaba utilizando.

Función 3FH

Uso:

Leer de un archivo abierto una cantdad definida de bytes y los almacena en un buffer específico.

Registros de llamada:

AH = 3FH

BX = Handle asignado

CX = Cantidad de bytes a leer

DS:DX = Apuntador a un área de trabajo.

Registros de retorno:

CF = 0 y AX = número de bytes leidos si no hubo error, en caso contrario CF = 1 y AX = código de error: 05H si acceso negado y 06H si no es válido el handle.

Función 40H

Uso:

349

Page 350: Programacion en Java

Escribe a un archivo ya abierto una cierta cantidad de bytes a partir del buffer designado.

Registros de llamada:

AH = 40H

BX = Handle asignado

CX = Cantidad de bytes a escribir.

DS:DX = Apuntador al buffer de datos.

Registros de retorno:

CF = 0 y AX = número de bytes escritos si no hay errores, en caso de existir CF = 1 y AX = código del error: 05H si el acceso es negado y 06H si el handle es inválido.

Función 42H

Uso:

Mover apuntador al archivo (Handle)

Registros de llamada:

AH = 42H

AL = método utilizado

BX = Handle asignado

CX = La parte más significativa del offset

DX = La parte menos significativa del offset

Registros de retorno:

CF = 0 y DX:AX = la nueva posición del apuntador. En caso de error CF será 1 y AX = código de error: 01H si la función no es válida y 06H si el handle no es válido.

El método utilizado se configura como sigue:

Valor de AL Método

00H A partir del principio del archivo

01H A partir de la posición actual

02H A partir del final del archivo

Interrupción 10H

350

Page 351: Programacion en Java

Propósito: Llamar a diversas funciones de video del BIOS.

Sintaxis:

Int 10H

Esta interrupción tiene diversas funciones, todas ellas nos sirven para controlar la entrada y salida de video, la forma de acceso a cada una de las opciones es por medio del registro AH.

En este tutorial unicamente veremos algunas de las funciones de esta interrupción.

• Funciones comunes de la interrupción 10H. • 02H Selección de posición del cursor • 09H Escribe atributo y caracter en el cursor • 0AH Escribe caracter en la posición del cursor • 0EH Escritura de caracteres en modo alfanumérico

Función 02H

Uso:

Posiciona el cursor en la pantalla dentro de las coordenadas válidas de texto.

Registros de llamada:

AH = 02H

BH = Página de video en la que se posicionará el cursor.

DH = Fila

DL = Columna

Registros de retorno:

Ninguno.

Las posiciones de localización del cursor son definidas por coordenadas iniciando en 0,0, que corresponde a la esquina superior izquierda hasta 79,24 correspondientes a la esquina inferior derecha. Tenemos entonces que los valores que pueden tomar los registros DH y DL en modo de texto de 80 x 25 son de 0 hasta 24 y de 0 hasta 79 respectivamente.

Función 09H

Uso:

Desplegar un caracter un determinado número de veces con un atributo definido empezando en la posición actual del cursor.

Registros de llamada:

351

Page 352: Programacion en Java

AH = 09H

AL = Caracter a desplegar

BH = Página de video en donde se desplegará

BL = Atributo a usar

Número de repeticiones.

Registros de retorno:

Ninguno

Esta función despliega un caracter el número de veces especificado en CX pero sin cambiar la posición del cursor en la pantalla.

Función 0AH

Uso:

Desplegar un caracter en la posición actual del cursor.

Registros de llamada:

AH = 0AH

AL = Caracter a desplegar

BH = Página en donde desplegar

BL = Color a usar (sólo en gráficos).

CX = Número de repeticiones

Registros de retorno:

Ninguno.

La única diferencia entre esta función y la anterior es que ésta no permite modificar los atributos, simplemente usa los atributos actuales.

Tampoco se altera la posición del cursor con esta función.

Función 0EH

Uso:

Deplegar un caracter en la pantalla actualizando la posición del cursor.

Registros de llamada:

352

Page 353: Programacion en Java

AH = 0EH

AL = Caracter a desplegar

BH = Página donde se desplegara el caracter

BL = Color a usar (solo en gráficos)

Registros de retorno:

Ninguno

Interrupción 16H

Propósito: Manejar la entrada/salida del teclado.

Sintaxis:

Int 16H

Veremos dos opciones de la interrupción 16H, estas opciones, al igual que las de otras interrupciones, son llamadas utilizando el registro AH.

• Funciones de la interrupción 16H • 00H Lee un caracter de teclado • 01H Lee estado del teclado

Función 00H

Uso:

Leer un caracter del teclado.

Registros de llamada:

AH = 00H

Registros de retorno:

AH = código de barrido (scan code) del teclado

AL = Valor ASCII del caracter.

Cuando se utiliza esta interrupción se detiene la ejecución del programa hasta que se introduzca un caracter desde el teclado, si la tecla presionada es un caracter ASCII su valor será guardado en el registro AH, de lo contrario el código de barrido será guardado en AL y AH contendrá el valor 00H.

El código de barrido fué creado para manejar las teclas que no tienen una representación ASCII como [ALT], [CONTROL], las teclas de función, etc.

353

Page 354: Programacion en Java

Función 01H

Uso:

Leer estado del teclado.

Registros de llamada:

AH = 01H

Registros de retorno:

Si la bandera de cero, ZF, está apagada significa que hay información en el buffer, si se encuentra prendida es que no hay teclas pendientes.

En caso de existir información el registro AH contendrá el código de la tecla guardada en el buffer.

Interrupción 17H

Propósito: Manejar la entrada/salida de la impresora.

Sintaxis:

Int 17H

Esta interrupción es utilizada para escribir caracteres a la impresora, inicializarla y leer su estado.

• Funciones de la interrupción 16H • 00H Imprime un caracter ASCII • 01H Inicializa la impresora • 02H Proporciona el estado de la impresora

Función 00H

Uso:

Escribir un caracter a la impresora.

Registros de llamada:

AH = 00H

AL = Caracter a imprimir

DX = Puerto a utilizar

Registros de retorno:

AH = Estado de la impresora.

El puerto a utilizar, definido en DX, se especifica así: LPT1 = 0, LPT2 = 1, LPT3 = 2 ...

354

Page 355: Programacion en Java

El estado de la impresora se codifica bit por bit como sigue:

BIT 1/0 SIGNIFICADO

----------------------------------------

0 1 Se agotó el tiempo de espera

1 -

2 -

3 1 Error de entrada/salida

4 1 Impresora seleccionada

5 1 Papel agotado

6 1 Reconocimiento de comunicación

7 1 La impresora se encuentra libre

Los bits 1 y 2 no son relevantes.

La mayoria de los BIOS unicamente soportan 3 puertos paralelos aunque existen algunos que soportan 4.

Función 01H

Uso:

Inicializar un puerto de impresión.

Registros de llamada:

AH = 01H

DX = Puerto a utilizar

Registros de retorno:

AH = Status de la impresora

El puerto a utilizar, definido en DX, se especifica así: LPT1 = 0, LPT2 = 1, etc.

El estado de la impresora se codifica bit por bit como sigue:

BIT 1/0 SIGNIFICADO

----------------------------------------

355

Page 356: Programacion en Java

0 1 Se agotó el tiempo de espera

1 -

2 -

3 1 Error de entrada/salida

4 1 Impresora seleccionada

5 1 Papel agotado

6 1 Reconocimiento de comunicación

7 1 La impresora se encuentra libre

Los bits 1 y 2 no son relevantes.

La mayoria de los BIOS unicamente soportan 3 puertos paralelos aunque existen algunos que soportan 4.

Función 02H

Uso:

Obtener el estado de la impresora.

Registros de llamada:

AH = 01H

DX = Puerto a utilizar

Registros de retorno:

AH = Status de la impresora.

El puerto a utilizar, definido en DX, se especifica así: LPT1 = 0, LPT2 = 1, etc.

El estado de la impresora se codifica bit por bit como sigue:

BIT 1/0 SIGNIFICADO

----------------------------------------

0 1 Se agotó el tiempo de espera

1 -

2 -

356

Page 357: Programacion en Java

3 1 Error de entrada/salida

4 1 Impresora seleccionada

5 1 Papel agotado

6 1 Reconocimiento de comunicación

7 1 La impresora se encuentra libre

Los bits 1 y 2 no son relevantes.

La mayoria de los BIOS unicamente soportan 3 puertos paralelos aunque existen algunos que soportan 4.

Métodos de trabajo con archivos

Existen dos formas de trabajar con archivos, la primera es por medio de bloques de control de archivos o "FCB" y la segunda es por medio de canales de comunicación, tambien conocidos como "handles".

La primera forma de manejo de archivos se viene utilizando desde el sistema operativo CPM, antecesor del DOS, por lo mismo asegura cierta compatibilidad con archivos muy antiguos tanto del CMP como de la versión 1.0 del DOS, además este método nos permite tener un número ilimitado de archivos abiertos al mismo tiempo. Si se quiere crear un volumen para el disco la única forma de lograrlo es utilizando este método.

Aún considerando las ventajas del FCB el uso de los canales de comunicación es mucho más sencillo y nos permite un mejor manejo de errores, además, por ser más novedoso es muy probable que los archivos así creados se mantengan compatibles a través de versiones posteriores del sistema operativo.

Para una mayor facilidad en las explicaciones posteriores me referiré a el método de bloques de control de archivos como FCBs y al método de canales de comunicación como handles.

Introducción

Existen dos tipos de FCB, el normal, cuya longitud es de 37 bytes y el extendido de 44 bytes. En este tutorial unicamente se tratará el primer tipo, así que de ahora en adelante cuando me refiera a un FCB realmente estoy hablando de un FCB de 37 bytes.

El FCB se compone de información dada por el programador y por información que toma directamente del sistema operativo. Cuando se utilizan este tipo de archivos unicamente es posible trabajar en el directorio actual ya que los FCB no proveen apoyo para el uso de la organización por directorios del DOS.

El FCB está formado por los siguientes campos:

POSICION LONGITUD SIGNIFICADO

00H 1 Byte Drive

357

Page 358: Programacion en Java

01H 8 Bytes Nombre del archivo

09H 3 Bytes Extensión

0CH 2 Bytes Número de bloque

0EH 2 Bytes Tamaño del registro

10H 4 Bytes Tamaño del archivo

14H 2 Bytes Fecha de creación

16H 2 Bytes Hora de creación

18H 8 Bytes Reservados

20H 1 Byte Registro actual

21H 4 Bytes Regsitro aleatorio

Para seleccionar el drive de trabajo se sigue el siguiente formato: drive A = 1; drive B = 2; etc. Si se utiliza 0 se tomará como opción el drive que se esté utilizando en ese momento.

El nombre del archivo debe estar justificado a la izquierda y en caso de ser necesario se deberán rellenar los bytes sobrantes con espacios, la extensión del archivo se coloca de la misma forma.

El bloque actual y el registro actual le dicen a la computadora que registro será accesado en operaciones de lectura o escritura. Un bloque es un grupo de 128 registros. El primer bloque del archivo es el bloque 0. El primer registro es el registro 0, por lo tanto el último registro del primer bloque sería 127, ya que la numeración inició con 0 y el bloque puede contener 128 registros en total.

Abrir archivos

Para abrir un archivo FCB se utiliza la interrupción 21H, función 0FH. La unidad, el nombre y extensión del archivo deben ser inicializados antes de abrirlo.

El registro DX debe apuntar al bloque. Si al llamar a la interrupción ésta regresa valor de FFH en el registro AH es que el archivo no se encontró, si todo salió bien se devolvera un valor de 0.

Si se abre el archivo DOS inicializa el bloque actual a 0, el tamaño del registro a 128 bytes y el tamaño del mismo y su fecha se llenan con los datos encontrados en el directorio.

Crear un archivo nuevo

Para la creación de archivos se utiliza la interrupción 21H función 16H .

DX debe apuntar a una estructura de control cuyos requisitos son que al menos se encuentre definida la unidad lógica, el nombre y la extensión del archivo.

En caso de existir algun problema se devolverá el valor FFH en AL, de lo contrario este registro contendrá el valor de 0.

358

Page 359: Programacion en Java

Escritura secuencial

Antes de que podamos realizar escrituras al disco es necesario definir el área de transferencia de datos utilizando para tal fin la función 1AH de la interrupción 21H.

La función 1AH no regresa ningún estado del disco ni de la operación, pero la función 15H, que es la que usaremos para escribir al disco, si lo hace en el registro AL, si éste es igual a cero no hubo error y se actualizan los campos del registro actual y bloque.

Lectura secuencial

Antes que nada debemos definir el área de transferencia de archivos o DTA.

Para leer secuencialmente utilizamos la función 14H de la int 21H.

El registro a ser leido es el que se encuentra definido por el bloque y el registro actual. El registro AL regresa el estado de la operación, si AL contiene el valor de 1 o 3 es que hemos llegado al final del archivo. Un resultado de 2 significa que el FCB está mal estructurado.

En caso de no existir error AL contendrá el valor de 0 y los campos bloque actual y registro actual son actualizados.

Lectura y escritura aleatoria

La función 21H y la función 22H de la interrupción 21H son las encargadas de realizar las lecturas y escrituras aleatorias respectivamente.

El número de registro aleatorio y el bloque actual son usados para calcular la posición relativa del registro a leer o escribir.

El registro AL regresa la misma información que para lectura o escritura secuencial. La información que será leída se regresará en el área de transferencia de disco, así mismo la información que será escrita reside en el DTA.

Cerrar un archivo

Para cerrar un archivo utilizamos la función 10H de la interrupción 21H.

Si después de invocarse esta función el registro AL contiene el valor de FFH significa que el archivo ha cambiado de posición, se cambió el disco o hay un error de acceso al disco.

Trabajando con handles

El uso de handles para manejar los archivos facilita en gran medida la creación de archivos y el programador puede concentrarse en otros aspectos de la programación sin preocuparse en detalles que pueden ser manejados por el sistema operativo.

La facilidad en el uso de los handles consiste en que para operar sobre un archivo unicamente es necesario definir el nombre del mismo y el número del handle a utilizar, toda la demás información es manejada internamente por el DOS.

359

Page 360: Programacion en Java

Cuando utilizamos este método para trabajar con archivos no existe una distinción entre accesos secuenciales o aleatorios, el archivo es tomado simplemente como una cadena de bytes.

Funciones para utilizar handles

Las funciones utilizadas para el manejo de archivos por medio de handles son descritas en la unidad 6: Interrupciones, en la sección dedicada a la interrupción 21H.

Definición de procedimiento

Un procedimiento es un conjunto de instrucciones a los que podemos dirigir el flujo de nuestro programa, y una vez terminada la ejecución de dichas instrucciones se devuelve el control a la siguiente linea a procesar del código que mando llamar al procedimiento.

Los procedimientos nos ayudan a crear programas legibles y fáciles de modificar.

Al momento de invocar a un procedimiento se guarda en la pila la dirección de la siguiente instrucción del programa para que, una vez transferido el flujo del programa y terminado el procedimiento, se pueda regresar a la linea siguiente del programa original (el que llamó al procedimiento).

Sintaxis de un procedimiento

Existen dos tipos de procedimientos, los intrasegmentos, que se encuentran en el mismo segmento de instrucciones y los intersegmentos que pueden ser almacenados en diferentes segmentos de memoria.

Cuando se utilizan los procedimientos intrasegmentos se almacena en la pila el valor de IP y cuando se utilizan los intersegmentos se almacena el valor CS:IP

Para desviar el flujo a un procedimiento (llamarlo) se utiliza la directiva:

CALL NombreDelProcedimiento

Las partes que componen a un procedimiento son:

• Declaración del procedimiento • Código del procedimiento • Directiva de regreso • Terminación del procedimiento

Por ejemplo, si queremos una rutina que nos sume dos bytes, almacenados en AH y AL cada uno y guardar la suma en el registro BX:

Suma Proc Near Declaración del procedimiento

Mov Bx, 0 Contenido del procedimiento

Mov Bl, Ah

Mov Ah, 00

Add Bx, Ax

360

Page 361: Programacion en Java

Ret ;Directiva de regreso

Suma Endp ;Declaración de final del procedimiento

En la declaración la primera palabra, Suma, corresponde al nombre de nuestro procedimiento, Proc lo declara como tal y la palabra Near le indica al MASM que el procedimiento es intrasegmento. La directiva Ret carga la dirección IP almacenada en la pila para regresar al programa original, por último, la directiva Suma Endp indica el final del procedimiento.

Para declarar un procedimiento intersegmento sustituimos la palabra Near por la palabra FAR.

El llamado de este procedimiento se realiza de la siguiente forma:

Call Suma

Las macros ofrecen una mayor flexibilidad en la programación comparadas con los procedimientos, pero no por ello se dejarán de utilizar estos últimos.

Definición de una macro

Una macro es un grupo de instrucciones repetitivas en un programa que se codifican solo una vez y pueden utilizarse cuantas veces sea necesario.

La principal diferencia entre una macro y un procedimiento es que en la macro se hace posible el paso de parámetros y en el procedimiento no (esto es aplicable solo para el MASM, hay otros lenguajes de programación que si lo permiten). Al momento de ejecutarse la macro cada parámetro es sustituido por el nombre o valor especificado al momento de llamarla.

Podemos decir entonces que un procedimiento es una extensión de un determinado programa, mientras que la macro es un módulo con funciones específicas que puede ser utilizado por diferentes programas.

Otra diferencia entre una macro y un procedimiento es la forma de llamar a cada uno, para llamar a un procedimiento se requiere el uso de una directiva, en cambio la llamada a las macros se realiza como si se tratara de una instrucción del ensamblador.

Sintaxis de una macro

Las partes que componen a una macro son:

• Declaración de la macro • Código de la macro • Directiva de terminación de la macro

La declaración de la macro se lleva a cabo de la siguiente forma:

NombreMacro MACRO [parametro1, parametro2...]

Aunque se tiene la funcionalidad de los parametros es posible crear una macro que no los necesite.

La directiva de terminación de la macro es: ENDM

361

Page 362: Programacion en Java

Un ejemplo de macro, para colocar el cursor en alguna posición determinada de la pantalla es:

Posicion MACRO Fila, Columna

PUSH AX

PUSH BX

PUSH DX

MOV AH, 02H

MOV DH, Fila

MOV DL, Columna

MOV BH, 0

INT 10H

POP DX

POP BX

POP AX

ENDM

Para utilizar una macro solo es necesario llamarla por su nombre, como si fuera una instrucción mas del ensamblador, ya no son necesarias las directivas como en el caso de los procedimientos. Ejemplo:

Posicion 8, 6

Bibliotecas de macros

Una de las facilidades que ofrece el uso de las macros es la creación de bibliotecas, las cuales son grupos de macros que pueden ser incluidas en un programa desde un archivo diferente.

La creación de estas bibliotecas es muy sencilla, únicamente tenemos que escribir un archivo con todas las macros que se necesitarán y guardarlo como archivo de texto.

Para llamar a estas macros solo es necesario utilizar la instrucción Include NombreDelArchivo, en la parte de nuestro programa donde escribiriamos normalmente las macros, esto es, al principio de nuestro programa (antes de la declaración del modelo de memoria).

Suponiendo que se guardó el archivo de las macros con el nombre de MACROS.TXT la instrucción Include se utilizaría de la siguiente forma:

;Inicio del programa

362

Page 363: Programacion en Java

Include MACROS.TXT

.MODEL SMALL

.DATA

;Aqui van los datos

.CODE

Inicio:

;Aqui se inserta el código del programa

.STACK

;Se define la pila

End Inicio

;Termina nuestro programa

363

Page 364: Programacion en Java

Prólogo General

Los Avances de la Ciencia y la Tecnología han puesto al hombre en un plano intermedio entre lo tangible e intangible computacionalmente hablando, es ahora tan común el convivir con un computador diariamente que cada vez se hace más imperativo la mejor interacción hombre-máquina a través de una adecuada interfaz (Interfaz de Usuario), que le brinde tanto comodidad ,como eficiencia.

El presente trabajo es una introducción al mundo de las Interfaz de Usuarios, en el están los conceptos y nociones básicas que permitirán en adelante adentrarnos más en este ámbito.

CONCEPTOS DE INTERFAZ

Lewis y Rieman [1993] definen las interfaces hombre computadora como:

Aquellas interfaces básicas que incluyen cosas u objetos como menús, ventanas, teclado, ratón, los “beeps” y algunos otros sonidos que la computadora hace, en general, todos aquellos canales por los cuales se permite la comunicación entre el hombre y la computadora.

La idea fundamental en el concepto de interfaz es el de mediación, entre hombre y máquina. La interfaz es lo que "media", lo que facilita la comunicación, la interacción, entre dos sistemas de diferente naturaleza, típicamente el ser humano y una máquina como el computador. Esto implica, además, que se trata de un sistema de traducción, ya que los dos "hablan" lenguajes diferentes: verbo-icónico en el caso del hombre y binario en el caso del procesador electrónico. De una manera más técnica se define a Interfaz de usuario, como conjunto de componentes empleados por los usuarios para comunicarse con las computadoras.

El usuario dirige el funcionamiento de la máquina mediante instrucciones, denominadas genéricamente entradas. Las entradas se introducen mediante diversos dispositivos, por ejemplo un teclado, y se convierten en señales electrónicas que pueden ser procesadas por la computadora. Estas señales se transmiten a través de circuitos conocidos como bus, y son coordinadas y controladas por la unidad de proceso central y por un soporte lógico conocido como sistema operativo. Una vez que la UPC ha ejecutado las instrucciones indicadas por el usuario, puede comunicar los resultados mediante señales electrónicas, o salidas, que se transmiten por el bus a uno o más dispositivos de salida, por ejemplo una impresora o un monitor.

Resumiendo entonces podemos decir que, una interfaz de software es la parte de una aplicación que el usuario ve y con la cual interactúa. Está relacionada con la subyacente estructura, la arquitectura, y el código que hace el trabajo del software, pero no se confunde con ellos. La interfaz incluye las pantallas, ventanas, controles, menús, metáforas, la ayuda en línea, la documentación y el entrenamiento. Cualquier cosa que el usuario ve y con lo cual interactúa es parte de la interfaz. Una interfaz inteligente es fácil de aprender y usar. Permite a los usuarios hacer su trabajo o desempeñar una tarea en la manera que hace más sentido para ellos, en vez de tener que ajustarse al software. Una interfaz inteligente se diseña específicamente para la gente que la usará.

CLASIFICACIÓN

Dentro de las Interfaces de Usuario se distinguir básicamente dos tipos:

Interfaz de usuario

364

Page 365: Programacion en Java

• Una interfaz de hardware, a nivel de los dispositivos utilizados para ingresar, procesar y entregar los datos: teclado, ratón y pantalla visualizadora; y

• Una interfaz de software, destinada a entregar información acerca de los procesos y herramientas de control, a través de lo que el usuario observa habitualmente en la pantalla.

De esta clasificación general se puede ir desprendiendo algunas, así por ejemplo según su evolución tenemos que las interfaces de usuario corren en paralelo con la de los sistemas operativos; de hecho, la interfaz constituye actualmente uno de los principales elementos de un sistema operativo.

A continuación se muestran las distintas interfaces que históricamente han ido apareciendo, ejemplificándolas con las sucesivas versiones de los sistemas operativos más populares.

Interfaces de línea de mandatos (command-line user interfaces, CUIs).

Es el característico del DOS, el sistema operativo de los primeros PC, y es el estilo más antiguo de interacción hombre-máquina. El usuario escribe órdenes utilizando un lenguaje formal con un vocabulario y una sintaxis propia (los mandatos en el caso del DOS). Se usa un teclado, típicamente, y las órdenes están encaminadas a realizar una acción.

El usuario no suele recibir mucha información por parte del sistema (ejemplo: indicador del DOS), y debe conocer cómo funciona el ordenador y dónde están los programas (nada está oculto al usuario). El modelo de la interfaz es el del programador, no el del usuario. Ejemplo del DIR-DEL-DIR, por la falta de información de respuesta del DOS. Otras veces, en cambio, es excesiva: etiqueta del volumen en el DIR.

Inconveniente: carga de memoria del usuario (debe memorizar los mandatos; incluso la ayuda es difícil de leer); nombres no siempre adecuados a las funciones, significado de los mandatos mal comprendido a veces (varios mandatos con el mismo o parecido significado, como DEL y ERASE); inflexible en los nombres (DEL y no DELETE).

Ventajas: potente, flexible y controlado por el usuario, aunque esto es una ventaja para usuarios experimentados. La sintaxis es estricta, y los errores pueden ser graves

Así:

C:\TMP\>dir

El volumen en unidad C es PCDOS_6

Número de Serie del Volumen es 1D8F-82B0

Directorio de C:\TMP

. <DIR> 02-02-98 21:08

.. <DIR> 02-02-98 21:08

ABCD <DIR> 02-02-98 21:23

365

Page 366: Programacion en Java

CARTA DOC 1.107 22-10-96 9:51

4 archivo(s) 1.107 bytes

24.862.720 bytes libres

C:\TMP\>

Problema del mandato COPY

En suma, un CUI es adecuado para usuarios expertos, no para noveles. Para aquellos resultan más rápidos, por lo que se puede diseñar un CUI como parte de una interfaz, para que se pueda utilizar una vez que se tenga experiencia.

Interfaces de menús

Un menú es una lista de opciones que se muestran en la pantalla o en una ventana de la pantalla para que los usuarios elijan la opción que deseen (véase ejemplo). Los menús permiten dos cosas: navegar dentro de un sistema, presentando rutas que llevan de un sitio a otro, y seleccionar elementos de una lista, que representan propiedades o acciones que los usuarios desean realizar sobre algún objeto.

Las interfaces de menús aparecen cuando el ordenador se vuelve una herramienta de usuario y no sólo de programadores. Las actuales interfaces gráficas u orientadas a objetos siguen utilizando este tipo de interfaces (los distintos estilos de interfaces no son mutuamente exclusivos).

Existen distintos tipos de menús. Los primeros fueron los menús de pantalla completa, estructurados jerárquicamente .

Veamos:

Menú de pantalla completa (Norton Utilities)

Los menús de barra, situados en la parte superior de la pantalla, son profusamente utilizados en las aplicaciones actuales. Contienen una lista de acciones genéricas que dan paso a menús desplegables donde se concretan.

366

Page 367: Programacion en Java

Menú de barra y menú desplegable

Estos menús pueden llevar a su vez a otros: son los menús en cascada. Pueden cambiar dinámicamente, y deshabilitar opciones que no estén disponibles en un momento dado (marcándolas habitualmente en gris).

Menús en cascada de la barra de inicio de Windows 95

Las paletas o barras de herramientas son menús gráficos con acciones, herramientas y opciones que se pueden colocar en la pantalla. Se utilizan mucho en programas gráficos.

367

Page 368: Programacion en Java

Paletas de herramientas en Microsoft Powerpoint

Los menús contextuales o menús pop-up son los más recientes. Se llaman así porque el contenido del menú depende del contexto de trabajo del usuario. Contienen únicamente las opciones que son aplicables al objeto seleccionado, más algunas de uso frecuente que también son accesibles desde el menú de barra.

Menú contextual de un icono en el escritorio de Windows 95

Las interfaces de menús, bien estructuradas, son buenas para usuarios noveles o esporádicos. Son fáciles de aprender y de recordar. Pueden existir menús simples y avanzados, para adaptarse al tipo de usuario. Precauciones: no ocupar demasiado espacio de la pantalla, recordar la información acumulada de menús precedentes, no colocar demasiados elementos en el menú, agruparlos de manera lógica (no en orden alfabético, por ejemplo; esto ayuda a recordarlos), permitir la personalización por parte del usuario, usar una terminología adecuada y consistente dentro del programa y con otros programas (Exit, Quit, Escape, Close, Return, Back). Las interfaces de menús serán utilizadas normalmente en conjunción con los otros estilos de interfaces.

Interfaces gráficas (graphical user interfaces, GUIs)

368

Page 369: Programacion en Java

Desarrolladas originalmente por XEROX (sistema Xerox Star, 1981, sin éxito comercial), aunque popularizadas por Apple (Steven Jobs se inspiró en los trabajos de Xerox y creó el Apple Lisa, 1983, sin éxito, y Apple Macintosh, 1984, con éxito debido en gran medida a su campaña publicitaria).

Los tres estilos más comunes de interfaces gráficas hombre-computadora son: Lo que tú ves es lo que puedes conseguir (WYSIWYG What you see is what you get), Manipulación directa e Interfaces de usuario basados en iconos.

Un GUI es una representación gráfica en la pantalla del ordenador de los programas, datos y objetos, así como de la interacción con ellos. Un GUI proporciona al usuario las herramientas para realizar sus operaciones, más que una lista de las posibles operaciones que el ordenador es capaz de hacer.

Características de un GUI:

1. Posee un monitor gráfico de alta resolución. 2. Posee un dispositivo apuntador (típicamente un ratón). 3. Promueve la consistencia de la interfaz entre programas. 4. Los usuarios pueden ver en la pantalla los gráficos y textos tal como se verán

impresos. 5. Sigue el paradigma de la interacción objeto-acción. 6. Permite la transferencia de información entre programas. 7. Se puede manipular en la pantalla directamente los objetos y la información. 8. Provee elementos de interfaz estándar como menús y diálogos. 9. Existe una muestra visual de la información y los objetos (iconos y ventanas). 10. Proporciona respuesta visual a las acciones del usuario. 11. Existe información visual de las acciones y modos del usuario/sistema (menús,

paletas). 12. Existen controles gráficos (widgets) para la selección e introducción de la

información. 13. Permite a los usuarios personalizar la interfaz y las interacciones. 14. Proporciona flexibilidad en el uso de dispositivos de entrada (teclado/ratón).

Una característica importante es que el GUI permite manipular los objetos e información de la pantalla, no sólo presentarla.

Para usar un GUI, los usuarios deben conocer (o aprender) una serie de conceptos: organización del sistema (ficheros, directorios en Win95), diferentes tipos de iconos y efecto de las acciones sobre ellos, elementos básicos de una ventana, uso de los controles del GUI, uso del ratón.

Los GUI usan el estilo objeto-acción, en contraposición al acción-objeto de los CUI o las interfaces de menú. El usuario selecciona un objeto, y después la acción a realizar sobre dicho objeto. Los objetos son el principal foco de atención del usuario, lo cual resulta más natural y próximo a su modelo mental.

369

Page 370: Programacion en Java

Metáfora de la cámara

Interfaces orientadas a objetos (object oriented user interfaces, OOUIs)

Su aspecto es similar al de las GUIs. La diferencia estriba en el modelo subyacente: las GUIs son interfaces orientadas a la aplicación, mientras que las OOUIs están orientadas al objeto. La tabla siguiente muestra las principales diferencias entre ambos estilos de interfaz:

Interfaces orientadas a la aplicación Interfaces orientadas a objetos

La aplicación consiste en un icono, una ventana principal y varias secundarias

El producto consiste en una colección de objetos que cooperan y vistas de dichos objetos

Los iconos representan aplicaciones o ventanas abiertas

Los iconos representan objetos que se pueden manipular directamente

Los usuarios deben abrir una aplicación antes de trabajar con objetos

Los usuarios abren objetos como vistas en el escritorio

Proporciona al usuario las funciones necesarias para realizar las tareas

Proporciona al usuario los materiales necesarios para realizar las tareas

Se centra en la tarea principal determinada por la aplicación

Se centra en las entradas y salidas de los objetos y tareas

Las tareas relacionadas son soportadas por otras aplicaciones

Las tareas relacionadas son soportadas por el uso de otros objetos

Estructura rígida: función Estructura flexible: objeto

Los usuarios pueden quedar atrapados en una tarea

Los usuarios no deben quedar atrapados en una tarea

Los usuarios deben seguir la estructura de la aplicación

Los usuarios pueden realizar tareas a su propio gusto

Se requieren muchas aplicaciones: una por tarea

Se requieren pocos objetos, que se reutilizan en muchas tareas

El objetivo de la OOUI es que el usuario se concentre en sus tareas en lugar de en el ordenador y cómo utilizar las aplicaciones y ficheros necesarios para cumplir sus objetivos. Por ello se esconde la organización del sistema al usuario (Ejemplo de los accesos directos en Windows95-OS/2).

El estilo de interacción de los OOUIs es el de objeto-acción (también se da en los GUIs, aunque mezclado con el estilo acción-objeto). La ventana es un objeto ventana, no una ventana de aplicación; desaparecen pues los menús de barra y ganan terreno los contextuales.

370

Page 371: Programacion en Java

Los objetos se pueden clasificar en tres categorías: datos, contenedores y dispositivos. Sobre ellos se definen distintas vistas (por ejemplo, la ayuda constituye una vista del objeto). Definir los objetos y las vistas es lo más complicado del diseño de la interfaz. El objeto debe ser familiar al usuario (encajar con su modelo mental, apoyado en su vida diaria), y estar relacionado con el mundo real: uso de las metáforas.

Distintas vistas del objeto reloj

Un ejemplo de lo que se pretende con una interfaz OOUI es el considerar un documento como un objeto sobre el cual realizar tareas tales como incorporar gráficos y textos, sin necesidad de usar programas distintos para cada una de ellas. Estos programas suelen tener funciones que se solapan, con el consiguiente gasto extra en espacio y dinero.

Actualmente existe una mezcla de productos orientados a la aplicación y al objeto, aunque se está produciendo una migración a estos últimos. Las aplicaciones están dejando paso a conjuntos de objetos.

CARACTERÍSTICAS HUMANAS DEL DISEÑO DE INTERFAZ

Factores Humanos

Al diseñar interfaces de usuario deben tenerse en cuenta las habilidades cognitivas y de percepción de las personas, y adaptar el programa a ellas.

Así, una de las cosas más importantes que una interfaz puede hacer es reducir la dependencia de las personas de su propia memoria, no forzándoles a recordar cosas innecesariamente (por ejemplo, información que apareció en una pantalla anterior) o a repetir operaciones ya realizadas (por ejemplo, introducir un mismo dato repetidas veces).

La persona tiene unas habilidades distintas de la máquina, y ésta debe utilizar las suyas para soslayar las de aquella (como por ejemplo la escasa capacidad de la memoria de corto alcance).

• Velocidad de Aprendizaje.- Se pretende que la persona aprenda a usar el sistema lo más pronto posible.

• Velocidad de Respuesta.- El tiempo necesario para realizar una operación en el sistema. • Tasa de errores.- Porcentaje de errores que comete el usuario. • Retención.- Cuánto recuerda el usuario sobre el uso del sistema en un período. de tiempo. • Satisfacción.- Se refiere a que el usuario esté a gusto con el sistema.

371

Page 372: Programacion en Java

Además de éstos existen otros a considerar:

Adecuación

• Características Físicas.- Cada persona tiene diferentes características físicas. Hay algunas personas que no les gustan los teclados mientras que a otras sí. Es por eso que hay teclados ergonómicos. Lo mismo sucede con el mouse.

• Ambiente.- El lugar donde va a ser usado el sistema. Cada interfaz tiene que adecuarse al lugar.

• Visibilidad.- Tomar en cuenta la cantidad de iluminación del lugar. ¿ Se refleja el brillo en la pantalla?

• Personalidad.- De acuerdo a la edad, nivel socio-económico, etc. • Cultura.- Los japoneses no tienen las mismas pantallas, ventanas, etc. Este factor es

importante si el mercado para el sistema es a nivel internacional.

Según la función tenemos:

Motivación

• Sistemas Vitales.- Son de vida o muerte; muchas personas dependen de ellos. Ejemplo: un sistema para reactores nucleares. Este sistema trabaja en tiempo real, y es de suma importancia la seguridad y efectividad del mismo.

• Sistemas Comerciales e Industriales.- Sirven para aumentar la productividad y vender más. • Sistemas de Oficina, Hogar y Juegos.- Factor importante: el mercado a quien está dirigido;

tienen que ser muy amigables y satisfacer al cliente. • Sistemas de Investigación.- Realizan tareas muy específicas y tratan de imitar el medio en

el que se desenvuelve el usuario.

PASOS PARA EL DISEÑO DE INTERFAZ

Pasos Clásicos

En el proceso de diseño de una interfaz de usuario se pueden distinguir cuatro fases o pasos fundamentales:

1. Reunir y analizar la información del usuario 2. Diseñar la interfaz de usuario 3. Construir la interfaz de usuario 4. Validar la interfaz de usuario

Reunir y analizar la información del usuario

Es decir concretar a través de técnicas de requerimentación, qué tipo de usuarios van a utilizar el programa, qué tareas van a realizar los usuarios y cómo las van a realizar, qué exigen los usuarios del programa, en qué entorno se desenvuelven los usuarios (físico, social, cultural).

Diseñar la interfaz de usuario

Es importante dedicar tiempo y recursos a esta fase, antes de entrar en la codificación. En esta fase se definen los objetivos de usabilidad del programa, las tareas del usuario, los objetos y acciones de la interfaz, los iconos, vistas y representaciones visuales de los objetos, los menús de

372

Page 373: Programacion en Java

los objetos y ventanas. Todos los elementos visuales se pueden hacer primero a mano y luego refinar con las herramientas adecuadas.

Construir la interfaz de usuario

Es interesante realizar un prototipo previo, una primera versión del programa que se realice rápidamente y permita visualizar el producto para poderlo probar antes de codificarlo definitivamente.

Validar la interfaz de usuario.

Se deben realizar pruebas de usabilidad del producto, a ser posible con los propios usuarios finales del mismo.

Es importante, en suma, realizar un diseño que parta del usuario, y no del sistema.

Existen 11 pasos en el proceso de diseño “centrado en las tareas”, similar al anterior pero que desglosa algunas actividades implícitas en otras, así:

1. Entender quien usará el sistema para hacer qué. 2. Elegir tareas representativas para el diseño. 3. Plagiar o copiar. 4. Bosquejar un diseño. 5. Pensar acerca del diseño. 6. Crear un prototipo. 7. Evaluarla con los usuarios. 8. Repetir. 9. Construirla. 10. Rastrearla. 11. Cambiarla.

Técnicas y pasos avanzadas para el diseño de interfaces de usuario

Presentación de información

No se deben colocar demasiados objetos en la pantalla, y los que existen deben estar bien distribuidos. Cada elemento visual influye en el usuario no sólo por sí mismo, sino también por su combinación con el resto de elementos presentes en la pantalla.

El número de elementos visuales que perciben son: en el caso a) 1 (el fondo); en b) 3 (la línea, lo que está encima y lo que está debajo); en c) son 5 (el espacio fuera del recuadro, el recuadro, la línea y el espacio encima y debajo de ésta); finalmente, en d) el número se eleva a 35, siguiendo el mismo criterio. Conclusión: cada elemento nuevo que se añade influye más de lo que se piensa en el usuario.

373

Page 374: Programacion en Java

Elementos de diseño de pantalla y su percepción visual

Análisis de Color:

Es probablemente el elemento de la interfaz que con más frecuencia es mal utilizado. El color comunica información, no es sólo decorativo (ejemplo: reforzar mensajes de error).

Deben utilizarse combinaciones adecuadas (por ejemplo, las paletas proporcionadas por los sistemas operativos). El color debe atraer la atención, pero no cansar después de un rato de trabajo.

Es especialmente importante seguir las líneas de diseño existentes. Principio básico: diseñar primero en blanco y negro, y luego añadir el color.

Análisis Audio

Primero es preciso ver cuándo es más apropiado que la información visual. Segundo, determinar el sonido adecuado. Tercero, permitir la personalización (volumen y desactivación). Como en el caso de los colores existen guías de uso. En lugares de trabajo abiertos, puede ser poco efectivo; además, puede ser embarazoso para algunas personas. El sonido debe usarse para informar, no cuando no añade nada nuevo (por ejemplo, un mensaje de aviso de correo o de bienvenida, respectivamente, al iniciar una sesión de trabajo).

Análisis Animación

Se define como un cambio en el tiempo de la apariencia visual de un elemento gráfico. Ejemplos de su uso: progreso de acciones (copia de ficheros en Windows 95, instalación de programas), estado de procesos (iconos de impresora), acciones posibles (cambios en el cursor al desplazar el ratón). La animación puede ayudar a subrayar iconos importantes, mostrar el estado de un objeto particular o explicar su comportamiento.

Diseño internacional

Debe hacerse un uso adecuado de la terminología. Hay mucho trabajo en este campo. Debe tenerse cuidado con las diferencias culturales (gestos, terminología, dibujos, formatos de teléfonos o calendarios, etc.).

374

Page 375: Programacion en Java

Análisis y Elección de controles

Muchas veces existe la duda de qué controles utilizar. En realidad no existe una única forma correcta. Un aspecto a considerar es la escalabilidad (menú de 10/1000 elementos; ejemplo: programas del menú inicio de Windows 95).

Ejemplo de alternativas

Usar un menú de barra o de paleta, permitir arrastrar objetos o no (problema: no existe indicación visual de que se pueda arrastrar el objeto: ¿qué objetos se pueden arrastrar? ¿a dónde se pueden arrastrar? ¿qué ocurrirá cuando lleguen allí? ¿se podrá deshacer la acción?).

Diferentes controles para los mismos datos

Guías de Expertos

Existen diversas guías de diseño sacadas de expertos y comités, que complementan a las reglas de oro estudiadas anteriormente. Por citar algunas de ellas:

• Demasiada simetría puede hacer las pantallas difíciles de leer. • Si se ponen objetos sin alinear, hacerlo drásticamente.

Asimetría=activo, simetría=sereno. • Elementos de tamaño y color similares se perciben como pertenecientes a un grupo. • Asumir errores en la entrada del usuario. • Diseñar para el usuario, no para demostrar los propios conocimientos tecnológicos. • Unos gráficos espectaculares no salvarán a una mala interfaz.

CONCLUSIONES Y RECOMENDACIONES

• El conocimiento de estos puntos clave, nos permitirán enfocarnos mejor al estudio de la materia.

• Las Interfaces de usuario, como vínculo de inmersión del hombre en el entorno de trabajo tecnológico actual, realzan su importancia en el desarrollo de nuevos productos, más eficaces, eficientes e interactivos, que es lo que el mercado demanda.

• Puntos, cómo los históricos y evolutivos, deben ser abordados de manera más investigativa, recordemos que “conocer el pasado nos proyecta al futuro”.

375

Page 376: Programacion en Java

• Otras puntualizaciones de clasificación obligarán a que investiguemos y propongamos, nuevas distribuciones clasificatorias, útiles a futuro en una carrera de desarrollo de software.

376

Page 377: Programacion en Java

Pruebas de programas

1. Introducción

Una de las últimas fases del ciclo de vida antes de entregar un programa para su explotación, es la fase de pruebas.

Una de las sorpresas con las que suelen encontrar los nuevos programadores es la enorme cantidad de tiempo y esfuerzo que requiere esta fase. Se estima que la mitad del esfuerzo de desarrollo de un programa (tanto en tiempo como en gastos) se va en esta fase. Si hablamos de programas que involucran vidas humanas (medicina, equipos nucleares, etc) el costo de la fase de pruebas puede fácilmente superar el 80%.

Pese a su enorme impacto en el coste de desarrollo, es una fase que muchos programadores aún consideran clasificable como un arte y, por tanto, como difícilmente conceptualizable. Es muy difícil entrenar a los nuevos programadores, que aprenderán mucho más de su experiencia que de lo que les cuenten en los cursos de programación.

Aún siendo una tarea abocada al fracaso, voy a intentarlo.

1.1. ¿Qué es probar?

Como parte que es de un proceso industrial, la fase de pruebas añade valor al producto que se maneja: todos los programas tienen errores y la fase de pruebas los descubre; ese es el valor que añade. El objetivo específico de la fase de pruebas es encontrar cuantos más errores, mejor.

Es frecuente encontrarse con el error de afirmar que el objetivo de esta fase es convencerse de que el programa funciona bien. En realidad ese es el objetivo propio de las fases anteriores (¿quién va a pasar a la sección de pruebas un producto que sospecha que está mal?). Cumplido ese objetivo, lo mejor posible, se pasa a pruebas. Esto no obsta para reconocer que el objetivo último de todo el proceso de fabricación de programas sea hacer programas que funcionen bien; pero cada fase tiene su objetivo específico, y el de las pruebas es destapar errores.

Probar un programa es ejercitarlo con la peor intención a fin de encontrarle fallos.

Por poner un ejemplo duro, probar un programa es equivalente a la actividad de ciertos profesores para los que examinar a un alumno consiste en poner en evidencia todo lo que no sabe. Esto es penoso cuando se aplica a personas; pero es exactamente lo que hay que hacerle a los programas.

1.2. La Prueba Exhaustiva es Imposible

La prueba ideal de un sistema sería exponerlo en todas las situaciones posibles, así encontraríamos hasta el último fallo. Indirectamente, garantizamos su respuesta ante cualquier caso que se le presente en la ejecución real.

Esto es imposible desde todos los puntos de vista: humano, económico e incluso matemático.

Dado que todo es finito en programación (el número de líneas de código, el número de variables, el número de valores en un tipo, etc etc) cabe pensar que el número de pruebas posibles es finito. Esto deja de ser cierto en cuanto entran en juego bucles, en los que es fácil introducir condiciones

377

Page 378: Programacion en Java

para un funcionamiento sin fin. Aún en el irrealista caso de que el número de posibilidades fuera finito, el número de combinaciones posibles es tan enorme que se hace imposible su identificación y ejecución a todos los efectos prácticos.

Probar un programa es someterle a todas las posible variaciones de los datos de entrada, tanto si son válidos como si no lo son. Imagínese hacer esto con un compilador de cualquier lenguaje: ¡habría que escribir, compilar y ejecutar todos y cada uno de los programas que se pudieran escribir con dicho lenguaje!

Sobre esta premisa de imposibilidad de alcanzar la perfección, hay que buscar formas humanamente abordables y ecónomicamente aceptables de encontrar errores. Nótese que todo es muy relativo y resbaladizo en este área.

1.3. Organización

Hay multitud de conceptos (y palabras clave) asociadas a las tareas de prueba. Clasificarlas es difícil, pues no son mutuamente disjuntas, sino muy entrelazadas. En lo que sigue intentaremos la siguiente estructura para la presentación:

Fases de prueba:

• UNIDADESPlanteamientos:

o CAJA BLANCACobertura:

de segmentos de ramas de condición/decisión de bucles

o CAJA NEGRACobertura de requisitos

• INTEGRACIÓN • ACEPTACIÓN

La prueba de unidades se plantea a pequeña escala, y consiste en ir probando uno a uno los diferentes módulos que constituyen una aplicación.

Las pruebas de integración y de aceptación son pruebas a mayor escala, que puede llegar a dimensiones industriales cuando el número de módulos es muy elevado, o la funcionalidad que se espera del programa es muy compleja.

Las pruebas de integración se centran en probar la coherencia semántica entre los diferentes módulos, tanto de semántica estática (se importan los módulos adecuados; se llama correctamente a los procedimientos proporcionados por cada módulo), como de semántica dinámica (un módulo recibe de otro lo que esperaba). Normalmente estas pruebas se van realizando por etapas, englobando progresivamente más y más módulos en cada prueba.

Las pruebas de integración se pueden empezar en cuanto tenemos unos pocos módulos, aunque no terminarán hasta disponer de la totalidad. En un diseño descendente (top-down) se empieza a probar por los módulos más generales; mientras que en un diseño ascendente se empieza a probar por los módulos de base.

378

Page 379: Programacion en Java

El planteamiento descendente tiene la ventaja de estar siempre pensando en términos de la funcionalidad global; pero también tiene el inconveniente de que para cada prueba hay que "inventarse" algo sencillito (pero fiable) que simule el papel de los módulos inferiores, que aún no están disponibles.

El planteamiento ascendente evita tener que escribirse módulos ficticios, pues vamos construyendo pirámides más y más altas con lo que vamos teniendo. Su desventaja es que se centra más en el desarrollo que en las espectativas finales del cliente.

Estas clasificaciones no son las únicas posibles. Por ejemplo, en sistemas con mucha interacción con el usuario es frecuente codificar sólo las partes de cada módulo que hacen falta para una cierta funcionalidad. Una vez probada, se añade otra funcionalidad y así hasta el final. Esto da lugar a un planteamiento más "vertical" de las pruebas. A veces se conoce como "codificación incremental".

Por último, las pruebas de aceptación son las que se plantea el cliente final, que decide qué pruebas va a aplicarle al producto antes de darlo por bueno y pagarlo. De nuevo, el objetivo del que prueba es encontrar los fallos lo antes posible, en todo caso antes de pagarlo y antes de poner el programa en producción.

2. Prueba de Unidades

¿Cómo se prueban módulos sueltos?

Normalmente cabe distinguir una fase informal antes de entrar en la fase de pruebas propiamente dicha. La fase informal la lleva a cabo el propio codificador en su despacho, y consiste en ir ejecutando el código para convencerse de que "básicamente, funciona". Esta fase suele consistir en pequeños ejemplos que se intentan ejecutar. Si el módulo falla, se suele utilizar un depurador para observar la evolución dinámica del sistema, localizar el fallo, y repararlo.

En lenguajes antiguos, poco rigurosos en la sintaxis y/o en la semantica de los programas, esta fase informal llega a ser muy dura, laboriosa, y susceptible de dejar pasar grandes errores sin que se note. En lenguajes modernos, con reglas estrictas, hay herramientas que permiten análisis exhaustivos de los aspectos estáticos de la semántica de los programas: tipado de las variables, ámbitos de visibilidad, parámetros de llamada a procedimientos, etc etc

Hay asimismo herramientas más sofisticadas capaces de emitir "opiniones" sobre un programa y alertar de construcciones arriesgadas, de expresiones muy complicadas (que se prestan a equivocaciones), etc. etc. A veces pueden prevenir sobre variables que pueden usarse antes de tomar algún valor (no inicializadas), variables que se cargan pero luego no se usan, y otras posibilidades que, sin ser necesariamente errores en sí mismas, sí suelen apuntar a errores de verdad.

Más adelante, cuando el módulo parece presentable, se entra en una fase de prueba sistemática. En esta etapa se empieza a buscar fallos siguiendo algún criterio para que "no se escape nada". Los criterios más habituales son los denominados de caja negra y de caja blanca.

Se dice que una prueba es de caja negra cuando prescinde de los detalles del código y se limita a lo que se ve desde el exterior. Intenta descubrir casos y circunstancias en los que el módulo no hace lo que se espera de él.

Por oposición al término "caja negra" se suele denominar "caja blanca" al caso contrario, es decir, cuando lo que se mira con lupa es el código que está ahí escrito y se intenta que falle. Quizás sea más propio la denominación de "pruebas de caja transparente".

379

Page 380: Programacion en Java

2.1. Caja blanca

Sinónimos:

• pruebas estructurales • pruebas de caja transparente

En estas pruebas estamos siempre observando el código, que las pruebas se dedican a ejecutar con ánimo de "probarlo todo". Esta noción de prueba total se formaliza en lo que se llama "cobertura" y no es sino una medida porcentual de ¿cuánto código hemos cubierto?

Hay diferentes posibilidades de definir la cobertura. Todas ellas intentan sobrevivir al hecho de que el número posible de ejecuciones de cualquier programa no trivial es (a todos los efectos prácticos) infinito. Pero si el 100% de cobertura es infinito, ningún conjunto real de pruebas pasaría de un infinitésimo de cobertura. Esto puede ser muy interesante para los matemáticos; pero no sirve para nada.

Cobertura de segmentos

A veces también denominada "cobertura de sentencias". Por segmento se entiende una secuencia de sentencias sin puntos de decisión. Como el ordenador está obligado a ejecutarlas una tras otra, es lo mismo decir que se han ejecutado todas las sentencias o todos los segmentos.

El número de sentencias de un programa es finito. Basta coger el código fuente e ir contando. Se puede diseñar un plan de pruebas que vaya ejercitando más y más sentencias, hasta que hayamos pasado por todas, o por una inmensa mayoría.

En la práctica, el proceso de pruebas termina antes de llegar al 100%, pues puede ser excesivamente laborioso y costoso provocar el paso por todas y cada una de las sentencias.

A la hora de decidir el punto de corte antes de llegar al 100% de cobertura hay que ser precavido y tomar en consideración algo más que el índice conseguido. En efecto, ocurre con harta frecuencia que los programas contienen código muerto o inalcanzable. Puede ser que este trozo del programa, simplemente "sobre" y se pueda prescindir de él; pero a veces significa que una cierta funcionalidad, necesaria, es inalcanzable: esto es un error y hay que corregirlo.

Cobertura de ramas

La cobertura de segmentos es engañosa en presencia de segmentos opcionales. Por ejemplo:

IF Condicion THEN EjecutaEsto; END;

Desde el punto de vista de cobertura de segmentos, basta ejecutar una vez, con éxito en la condición, para cubrir todas las sentencias posibles. Sin embargo, desde el punto de vista de la lógica del programa, también debe ser importante el caso de que la condición falle (si no lo fuera, sobra el IF). Sin embargo, como en la rama ELSE no hay sentencias, con 0 ejecuciones tenemos el 100%.

380

Page 381: Programacion en Java

Para afrontar estos casos, se plantea un refinamiento de la cobertura de segmentos consistente en recorrer todas las posibles salidas de los puntos de decisión. Para el ejemplo de arriba, para conseguir una cobertura de ramas del 100% hay que ejecutar (al menos) 2 veces, una satisfaciendo la condición, y otra no.

Estos criterios se extienden a las construcciones que suponen elegir 1 de entre varias ramas. Por ejemplo, el CASE.

Nótese que si lograramos una cobertura de ramas del 100%, esto llevaría implícita una cobertura del 100% de los segmentos, pues todo segmento está en alguna rama. Esto es cierto salvo en programas triviales que carecen de condiciones (a cambio, basta 1 sóla prueba para cubrirlo desde todos los puntos de vista). El criterio también debe refinarse en lenguajes que admiten excepciones (por ejemplo, Ada). En estos casos, hay que añadir pruebas para provocar la ejecución de todas y cada una de las excepciones que pueden dispararse.

Cobertura de condición/decisión

La cobertura de ramas resulta a su vez engañosa cuando las expresiones booleanas que usamos para decidir por qué rama tirar son complejas. Por ejemplo:

IF Condicion1 OR Condicion2 THEN HazEsto; END;

Las condiciones 1 y 2 pueden tomar 2 valores cada una, dando lugar a 4 posibles combinaciones. No obstante sólo hay dos posibles ramas y bastan 2 pruebas para cubrirlas. Pero con este criterio podemos estar cerrando los ojos a otras combinaciones de las condiciones.

Consideremos sobre el caso anterior las siguientes pruebas:

Prueba 1: Condicion1 = TRUE y Condicion2 = FALSE Prueba 2: Condicion1 = FALSE y Condicion2 = TRUE Prueba 3: Condicion1 = FALSE y Condicion2 = FALSE Prueba 4: Condicion1 = TRUE y Condicion2 = TRUE

Bastan las pruebas 2 y 3 para tener cubiertas todas las ramas. Pero con ellos sólo hemos probado una posibilidad para la Condición1.

Para afrontar esta problemática se define un criterio de cobertura de condición/decisión que trocea las expresiones booleanas complejas en sus componentes e intenta cubrir todos los posibles valores de cada uno de ellos.

Nótese que no basta con cubrir cada una de las condciones componentes, si no que además hay que cuidar de sus posibles combinaciones de forma que se logre siempre probar todas y cada una de las ramas. Así, en el ejemplo anterior no basta con ejecutar las pruebas 1 y 2, pues aun cuando cubrimos perfectamente cada posibilidad de cada condición por separado, lo que no hemos logrado es recorrer las dos posibles ramas de la decisión combinada. Para ello es necesario añadir la prueba 3.

El conjunto mínimo de pruebas para cubrir todos los aspectos es el formado por las pruebas 3 y 4. Aún así, nótese que no hemos probado todo lo posible. Por ejemplo, si en el programa nos colamos y ponemos AND donde queríamos poner OR (o viceversa), este conjunto de pruebas no lo detecta. Sólo queremos decir que la cobertura es un criterio útil y práctico; pero no es prueba exhaustiva.

381

Page 382: Programacion en Java

Cobertura de bucles

Los bucles no son más que segmentos controlados por decisiones. Así, la cobertura de ramas cubre plenamente la esencia de los bucles. Pero eso es simplemente la teoría, pues la práctica descubre que los bucles son una fuente inagotable de errores, todos triviales, algunos mortales. Un bucle se ejecuta un cierto número de veces; pero ese número de veces debe ser muy preciso, y lo más normal es que ejecutarlo una vez de menos o una vez de más tenga consecuencias indeseables. Y, sin embargo, es extremadamente fácil equivocarse y redactar un bucle que se ejecuta 1 vez de más o de menos.

Para un bucle de tipo WHILE hay que pasar 3 pruebas

1. 0 ejecuciones 2. 1 ejecución 3. más de 1 ejecución

Para un bucle de tipo REPEAT hay que pasar 2 pruebas

1. 1 ejecución 2. más de 1 ejecución

Los bucles FOR, en cambio, son muy seguros, pues en su cabecera está definido el número de veces que se va a ejecutar. Ni una más, ni una menos, y el compilador se encarga de garantizarlo. Basta pues con ejecutarlos 1 vez.

No obstante, conviene no engañarse con los bucles FOR y examinar su contenido. Si dentro del bucle se altera la variable de control, o el valor de alguna variable que se utilice en el cálculo del incremento o del límite de iteración, entonces eso es un bucle FOR con trampa.

También tiene "trampa" si contiene sentencias del tipo EXIT (que algunos lenguajes denominan BREAK) o del tipo RETURN. Todas ellas provocan terminaciones anticipadas del bucle.

Estos últimos párrafos hay que precisarlos para cada lenguaje de programación. Lo peor son aquellos lenguajes que permiten el uso de sentencias GOTO. Tampoco conviene confiarse de lo que prometen lenguajes como MODULA-2, que se supone que prohiben ciertas construcciones arriesgadas. Los compiladores reales suelen ser más tolerantes que lo que anuncian los libros.

Si el programa contiene bucles LOOP, o simplemente bucles con trampa, la única cobertura aplicable es la de ramas. El riesgo de error es muy alto; pero no se conocen técnicas sistemáticas de abordarlo, salvo reescribir el código.

Y en la práctica ¿qué hago?Tanta definición acaba resultando un tanto académica e inútil.

En la práctica de cada día, se suele procura alcanzar una cobertura cercana al 100% de segmentos. Es muy recomendable (aunque cuesta más) conseguir una buena cobertura de ramas. En cambio, no suele hacer falta ir a por una cobertura de decisiones atomizadas.

¿Qué es una buena cobertura?Pues depende de lo crítico que sea el programa. Hay que valorar el riesgo (o coste) que implica un

382

Page 383: Programacion en Java

fallo si éste se descubre durante la aplicación del programa. Para la mayor parte del software que se produce en Occidente, el riesgo es simplemente de imagen (si un juego fallece a mitad, queda muy feo; pero no se muere nadie). En estas circunstancias, coberturas del 60-80% son admisibles.

La cobertura requerida suele ir creciendo con el ámbito previsto de distribución. Si un programa se distribuye y falla en algo grave puede ser necesario redistribuirlo de nuevo y urgentemente. Si hay millones de clientes dispersos por varios paises, el coste puede ser brutal. En estos casos hay que exprimir la fase de pruebas para que encuentre prácticamente todos los errores sin pasar nada por alto. Esto se traduce al final en buscar coberturas más altas.

Es aún más delicado cuando entramos en aplicaciones que involucran vidas humanas (aplicaciones sanitarias, centrales nucleares, etc) Cuando un fallo se traduce en una muerte, la cobertura que se busca se acerca al 99% y además se presta atención a las decisiones atómicas.

También se suele perseguir coberturas muy elevadas (por encima del 90%) en las aplicaciones militares. Esto se debe a que normalmente van a ser utilizadas en condiciones muy adversas donde el tiempo es inestimable. Si un programa fallece, puede no haber una segunda oportunidad de arrancarlo de nuevo.

La ejecución de pruebas de caja blanca puede llevarse a cabo con un depurador (que permite le ejecución paso a paso), un listado del módulo y un rotulador para ir marcando por dónde vamos pasando. Esta tarea es muy tediosa, pero puede ser automatizada. Hay compiladores que a la hora de generar código máquina dejan incrustado en el código suficiente código como para poder dejar un fichero (tras la ejecución) con el número de veces que se ha ejecutado cada sentencia, rama, bucle, etc.

LimitacionesLograr una buena cobertura con pruebas de caja blanca es un objetivo deseable; pero no suficiente a todos los efectos. Un programa puede estar perfecto en todos sus términos, y sin embargo no servir a la función que se pretende.

Por ejemplo, un Rolls-Royce es un coche que sin duda pasaría las pruebas más exigentes sobre los últimos detalles de su mecánica o su carrocería. Sin embargo, si el cliente desea un todo-terreno, difícilmente va a comprárselo.

Por ejemplo, si escribimos una rutina para ordenar datos por orden ascendente, pero el cliente los necesita en orden decreciente; no hay prueba de caja blanca capaz de detectar la desviación.

Las pruebas de caja blanca nos convencen de que un programa hace bien lo que hace; pero no de que haga lo que necesitamos.

2.2. Caja negra

Sinónimos:

• pruebas de caja opaca • pruebas funcionales • pruebas de entrada/salida • pruebas inducidas por los datos

Las pruebas de caja negra se centran en lo que se espera de un módulo, es decir, intentan encontrar casos en que el módulo no se atiene a su especificación. Por ello se denominan pruebas

383

Page 384: Programacion en Java

funcionales, y el probador se limita a suministrarle datos como entrada y estudiar la salida, sin preocuparse de lo que pueda estar haciendo el módulo por dentro.

Las pruebas de caja negra están especialmente indicadas en aquellos módulos que van a ser interfaz con el usuario (en sentido general: teclado, pantalla, ficheros, canales de comunicaciones, etc etc) Este comentario no obsta para que sean útiles en cualquier módulo del sistema.

Las pruebas de caja negra se apoyan en la especificación de requisitos del módulo. De hecho, se habla de "cobertura de especificación" para dar una medida del número de requisitos que se han probado. Es fácil obtener coberturas del 100% en módulos internos, aunque puede ser más laborioso en módulos con interfaz al exterior. En cualquier caso, es muy recomendable conseguir una alta cobertura en esta línea.

El problema con las pruebas de caja negra no suele estar en el número de funciones proporcionadas por el módulo (que siempre es un número muy limitado en diseños razonables); sino en los datos que se le pasan a estas funciones. El conjunto de datos posibles suele ser muy amplio (por ejemplo, un entero).

A la vista de los requisitos de un módulo, se sigue una técnica algebráica conocida como "clases de equivalencia". Esta técnica trata cada parámetro como un modelo algebráico donde unos datos son equivalentes a otros. Si logramos partir un rango excesivamente amplio de posibles valores reales a un conjunto reducido de clases de equivalencia, entonces es suficiente probar un caso de cada clase, pues los demás datos de la misma clase son equivalentes.

El problema está pues en identificar clases de equivalencia, tarea para la que no existe una regla de aplicación universal; pero hay recetas para la mayor parte de los casos prácticos:

• si un parámetro de entrada debe estar comprendido en un cierto rango, aparecen 3 clases de equivalencia: por debajo, en y por encima del rango.

• si una entrada requiere un valor concreto, aparecen 3 clases de equivalencia: por debajo, en y por encima del rango.

• si una entrada requiere un valor de entre los de un conjunto, aparecen 2 clases de equivalencia: en el conjunto o fuera de él.

• si una entrada es booleana, hay 2 clases: si o no. • los mismos criterios se aplican a las salidas esperadas: hay que intentar generar

resultados en todas y cada una de las clases.

Ejemplo: utilizamos un entero para identificar el día del mes. Los valores posibles están en el rango [1..31]. Así, hay 3 clases:

1. números menores que 1 2. números entre 1 y 31 3. números mayores que 31

Durante la lectura de los requisitos del sistema, nos encontraremos con una serie de valores singulares, que marcan diferencias de comportamiento. Estos valores son claros candidatos a marcar clases de equivalencia: por abajo y por arriba.

Una vez identificadas las clases de equivalencia significativas en nuestro módulo, se procede a coger un valor de cada clase, que no esté justamente al límite de la clase. Este valor aleatorio, hará las veces de cualquier valor normal que se le pueda pasar en la ejecución real.

384

Page 385: Programacion en Java

La experiencia muestra que un buen número de errores aparecen en torno a los puntos de cambio de clase de equivalencia. Hay una serie de valores denominados "frontera" (o valores límite) que conviene probar, además de los elegidos en el párrafo anterior. Usualmente se necesitan 2 valores por frontera, uno justo abajo y otro justo encima.

LimitacionesLograr una buena cobertura con pruebas de caja negra es un objetivo deseable; pero no suficiente a todos los efectos. Un programa puede pasar con holgura millones de pruebas y sin embargo tener defectos internos que surgen en el momento más inoportuno (Murphy no olvida).

Por ejemplo, un PC que contenga el virus Viernes-13 puede estar pasando pruebas de caja negra durante años y años. Sólo falla si es viernes y es día 13; pero ¿a quién se le iba a ocurrir hacer esa prueba?

Las pruebas de caja negra nos convencen de que un programa hace lo que queremos; pero no de que haga (además) otras cosas menos aceptables.

3. Pruebas de Integración

Las pruebas de integración se llevan a cabo durante la construcción del sistema, involucran a un número creciente de módulos y terminan probando el sistema como conjunto.

Estas pruebas se pueden plantear desde un punto de vista estructural o funcional.

Las pruebas estructurales de integración son similares a las pruebas de caja blanca; pero trabajan a un nivel conceptual superior. En lugar de referirnos a sentencias del lenguaje, nos referiremos a llamadas entre módulos. Se trata pues de identificar todos los posibles esquemas de llamadas y ejercitarlos para lograr una buena cobertura de segmentos o de ramas.

Las pruebas funcionales de integración son similares a las pruebas de caja negra. Aquí trataremos de encontrar fallos en la respuesta de un módulo cuando su operación depende de los servicios prestados por otro(s) módulo(s). Según nos vamos acercando al sistema total, estas pruebas se van basando más y más en la especificación de requisitos del usuario.

Las pruebas finales de integración cubren todo el sistema y pretenden cubrir plenamente la especificación de requisitos del usuario. Además, a estas alturas ya suele estar disponible el manual de usuario, que también se utiliza para realizar pruebas hasta lograr una cobertura aceptable.

En todas estas pruebas funcionales se siguen utilizando las técnicas de partición en clases de equivalencia y análisis de casos límite (fronteras).

4. Pruebas de Aceptación

Estas pruebas las realiza el cliente. Son básicamente pruebas funcionales, sobre el sistema completo, y buscan una cobertura de la especificación de requisitos y del manual del usuario. Estas pruebas no se realizan durante el desarrollo, pues sería impresentable de cara al cliente; sino una vez pasadas todas las pruebas de integración por parte del desarrollador.

La experiencia muestra que aún despues del más cuidadoso proceso de pruebas por parte del desarrollador, quedan una serie de errores que sólo aparecen cuando el cliente se pone a usarlo. Los desarrolladores se suelen llevar las manos a la cabeza:

385

Page 386: Programacion en Java

"Pero, ¿a quién se le ocurre usar así mi programa?"

Sea como sea, el cliente siempre tiene razón. Decir que los requisitos no estaban claros, o que el manual es ambiguo puede salvar la cara; pero ciertamente no deja satisfecho al cliente. Alegar que el cliente es un inútil es otra tentación muy fuerte, que conviene reprimir.

Por estas razones, muchos desarrolladores ejercitan unas técnicas denominadas "pruebas alfa" y "pruebas beta". Las pruebas alfa consisten en invitar al cliente a que venga al entorno de desarrollo a probar el sistema. Se trabaja en un entorno controlado y el cliente siempre tiene un experto a mano para ayudarle a usar el sistema y para analizar los resultados.

Las pruebas beta vienen despues de las pruebas alfa, y se desarrollan en el entorno del cliente, un entorno que está fuera de control. Aquí el cliente se queda a solas con el producto y trata de encontrarle fallos (reales o imaginarios) de los que informa al desarrollador.

Las pruebas alfa y beta son habituales en productos que se van a vender a muchos clientes. Algunos de los potenciales compradores se prestan a estas pruebas bien por ir entrenando a su personal con tiempo, bien a cambio de alguna ventaja económica (mejor precio sobre el producto final, derecho a mantenimiento gratuito, a nuevas versiones, etc etc). La experiencia muestra que estas prácticas son muy eficaces.

5. Otros tipos de pruebas

Recorridos (walkthroughs)

Quizás es una técnica más aplicada en control de calidad que en pruebas. Consiste en sentar alrededor de una mesa a los desarrolladores y a una serie de críticos, bajo las órdenes de un moderador que impida un recalentamiento de los ánimos. El método consiste en que los revisores se leen el programa línea a línea y piden explicaciones de todo lo que no está meridianamente claro. Puede que simplemente falte un comentario explicativo, o que detecten un error auténtico o que simplemente el código sea tan complejo de entender/explicar que más vale que se rehaga de forma más simple. Para un sistema complejo pueden hacer falta muchas sesiones.

Esta técnica es muy eficaz localizando errores de naturaleza local; pero falla estrepitosamente cuando el error deriva de la interacción entre dos partes alejadas del programa. Nótese que no se está ejecutando el programa, sólo mirándolo con lupa, y de esta forma sólo se ve en cada instante un trocito del listado.

Aleatorias (random testing)

Ciertos autores consideran injustificada una aproximación sistemática a las pruebas. Alegan que la probabilidad de descubrir un error es prácticamente la misma si se hacen una serie de pruebas aleatoriamente elegidas, que si se hacen siguiendo las instrucciones dictadas por criterios de cobertura (caja negra o blanca).

Como esto es muy cierto, probablemente sea muy razonable comenzar la fase de pruebas con una serie de casos elegidos al azar. Esto pondrá de manifiesto los errores más patentes. No obstante, pueden permanecer ocultos errores más sibilinos que sólo se muestran ante entradas muy precisas.

386

Page 387: Programacion en Java

Si el programa es poco crítico (una aplicación personal, un juego, ...) puede que esto sea suficiente. Pero si se trata de una aplicación militar o con riesgo para vidas humanas, es de todo punto insuficiente.

Solidez (robustness testing)

Se prueba la capacidad del sistema para salir de situaciones embarazosas provocadas por errores en el suministro de datos. Estas pruebas son importantes en sistemas con una interfaz al exterior, en particular cuando la interfaz es humana.

Por ejemplo, en un sistema que admite una serie de órdenes (commands) se deben probar los siguientes extremos:

• órdenes correctas, todas y cada una • órdenes con defectos de sintaxis, tanto pequeñas desviaciones como errores de bulto • órdenes correctas, pero en orden incorrecto, o fuera de lugar • la orden nula (línea vacia, una o más) • órdenes correctas, pero con datos de más • provocar una interrupción (BREAK, ^C, o lo que corresponda al sistema soporte) justo después de introducir una orden. • órdenes con delimitadores inapropiados (comas, puntos, ...) • órdenes con delimitadores incongruentes consigo mismos (por ejemplo, esto]

Aguante (stress testing)

En ciertos sistemas es conveniente saber hasta dónde aguantan, bien por razones internas (¿hasta cuantos datos podrá procesar?), bien externas (¿es capaz de trabajar con un disco al 90%?, ¿aguanta una carga de la CPU del 90?, etc etc)

Prestaciones (performance testing)

A veces es importante el tiempo de respuesta, u otros parámetros de gasto. Típicamente nos puede preocupar cuánto tiempo le lleva al sistema procesar tantos datos, o cuánta memoria consume, o cuánto espacio en disco utiliza, o cuántos datos transfiere por un canal de comunicaciones, o ... Para todos estos parámetros suele ser importante conocer cómo evolucionan al variar la dimensión del problema (por ejemplo, al duplicarse el volumen de datos de entrada).

Conformidad u Homologación (conformance testing)

En programas de comunicaciones es muy frecuente que, además de los requisitos específicos del programa que estamos construyendo, aparezca alguna norma más amplia a la que el programa deba atenerse. Es frecuente que organismos internacionales como ISO y el CCITT elaboren especificaciones de referencia a las que los diversos fabricantes deben atenerse para que sus ordenadores sean capaces de entenderse entre sí.

Las pruebas, de caja negra, que se le pasan a un producto para detectar discrepancias respecto a una norma de las descritas en el párrafo anterior se denominan de conformidad u homologación. Suelen realizarse en un centro especialmente acreditado al efecto y, si se pasan satisfactoriamente, el producto recibe un sello oficial que dice: "homologado".

387

Page 388: Programacion en Java

Interoperabilidad (interoperability tesing)

En el mismo escenario del punto anterior, programas de comunicaciones que deden permitir que dos ordenadores se entiendan, aparte de las pruebas de conformidad se suelen correr una serie de pruebas, también de caja negra, que involucran 2 o más productos, y buscan problemas de comunicación entre ellos.

Regresión (regression testing)

Todos los sistemas sufren una evolución a lo largo de su vida activa. En cada nueva versión se supone que o bien se corrigen defectos, o se añaden nuevas funciones, o ambas cosas. En cualquier caso, una nueva versión exige una nueva pasada por las pruebas. Si éstas se han sistematizado en una fase anterior, ahora pueden volver a pasarse automáticamente, simplemente para comprobar que las modificaciones no provocan errores donde antes no los había.

El mínimo necesario para usar unas pruebas en una futura revisión del programa es una documentación muy muy clara.

Las pruebas de regresión son particularmente espectaculares cuando se trata de probar la interacción con un agente externo. Existen empresas que viven de comercializar productos que "graban" la ejecución de una prueba con operadores humanos para luego repetirla cuantas veces haga falta "reproduciendo la grabación". Y, obviamente, deben monitorizar la respuesta del sistema en ambos casos, compararla, y avisar de cualquier discrepancia significativa.

Mutación (mutation testing)

Es una técnica curiosa consistente en alterar ligeramente el sistema bajo pruebas (introduciendo errores) para averiguar si nuestra batería de pruebas es capaz de detectarlo. Si no, más vale introducir nuevas pruebas. Todo esto es muy laborioso y francamente artesano.

6. Depuración (debugging)

Casi todos los compiladores suelen llevar asociada la posibilidad de ejecutar un programa paso a paso, permitiéndole al operador conocer dónde está en cada momento, y cuánto valen las variables.

Los depuradores pueden usarse para realizar inspecciones rigurosas sobre el comportamiento dinámico de los programas. La práctica demuestra, no obstante, que su uso es tedioso y que sólo son eficaces si se persigue un objetivo muy claro. El objetivo habitual es utilizarlo como consecuencia de la detección de un error. Si el programa se comporta mal en un cierto punto, hay que averiguar la causa precisa para poder repararlo. La causa a veces es inmediata (por ejemplo, un operador booleano equivocado); pero a veces depende del valor concreto de los datos en un cierto punto y hay que buscar la causa en otra zona del programa.

En general es mala idea "correr al depurador", tanto por el tiempo que se pierde buceando sin una meta clara, como por el riesgo de corregir defectos intermedios sin llegar a la raiz del problema. Antes de entrar en el depurador hay que delimitar el error y sus posibles causas. Ante una prueba que falla, hay que identificar el dominio del fallo, averiguar las características de los datos que provoca el fallo (y comprobar experimentalmente que todos los datos con esas características provocan ese fallo, y los que no las tienen no lo provocan).

388

Page 389: Programacion en Java

El depurador es el último paso para convencernos de nuestro análisis y afrontar la reparación con conocimiento de causa.

7. Plan de Pruebas

Un plan de pruebas está constituido por un conjunto de pruebas. Cada prueba debe

• dejar claro qué tipo de propiedades se quieren probar (corrección, robustez, fiabilidad, amigabilidad, ...)

• dejar claro cómo se mide el resultado • especificar en qué consiste la prueba (hasta el último detalle de cómo se ejecuta) • definir cual es el resultado que se espera (identificación, tolerancia, ...) ¿Cómo se decide

que el resultado es acorde con lo esperado?

Las pruebas angelicales carecen de utilidad, tanto si no se sabe exactamente lo que se quiere probar, o si no está claro cómo se prueba, o si el análisis del resultado se hace "a ojo".

Estas mismas ideas se suelen agrupar diciendo que un caso de prueba consta de 3 bloques de información:

1. El propósito de la prueba 2. Los pasos de ejecución de la prueba 3. El resultado que se espera

Y todos y cada uno de esos puntos debe quedar perfectamente documentado. Las pruebas de usar y tirar más vale que se tiren directamente, aún antes de usarlas.

Cubrir estos puntos es muy laborioso y, con frecuencia, tedioso, lo que hace desagradable (o al menos muy aburrida) la fase de pruebas. Es mucho mas divertido codificar que probar. Tremendo error en el que, no obstante, es fácil incurrir.

Respecto al orden de pruebas, una práctica frecuente es la siguiente:

1. Pasar pruebas de caja negra analizando valores límite. Recuerde que hay que analizar condiciones límite de entrada y de salida.

2. Identificar clases de equivalencia de datos (entrada y salida) y añadir más pruebas de caja negra para contemplar valores normales (en las clases de equivalencia en que estos sean diferentes de los valores límite; es decir, en rangos amplios de valores).

3. Añadir pruebas basadas en "presunción de error". A partir de la experiencia y el sentido común, se aventuran situaciones que parecen proclives a padecer defectos, y se buscan errores en esos puntos. Son pruebas del tipo "¡Me lo temía!"

4. Medir la cobertura de caja blanca que se ha logrado con las fases previas y añadir más pruebas de caja blanca hasta lograr la cobertura deseada. Normalmente se busca una buena cobertura de ramas (revise los comentarios expuestos al hablar de caja blanca).

8. Aspectos Sicológicos y Organización del Trabajo

Parecen tonterías; pero pueden cambiar radicalmente el éxito de una fase de pruebas:

1. Probar es ejercitar un programa para encontrarle fallos.Jamás se debería probar un programa con el ánimo de mostrar que funciona; ese no es el objetivo.

389

Page 390: Programacion en Java

2. Un caso de prueba tiene éxito cuando encuentra un fallo.Lo gracioso no es encontrar un caso en el que el programa funciona perfectamente. Eso es, simplemente, lo normal. Lo guai es encontrar el caso en el que falla.

3. Las pruebas debe diseñarlas y pasarlas una persona distinta de la que ha escrito el código; es la única forma de no ser "comprensivo con los fallos".

Hacer una "obra maestra" cuesta mucho esfuerzo y requiere gran habilidad. Encontrarle fallos a una "obra maestra" cuesta aún más esfuerzo y exige otro tipo de habilidad.

4. Las pruebas no pueden esperar a que esté todo el código escrito para empezar a pasarlas. Deben irse pasando pruebas según se va generando el código para descubrir los errores lo antes posible y evitar que se propaguen a otros módulos. En realidad el nombre "fase de pruebas" es engañoso, pues hay muchas actividades que se desarrollan concurrentemente o, al menos, no se necesita cerrar una fase antes de pasar a la siguiente. Algunos autores llegan al extremo de afirmar que "primero hay que probar y luego codificar". Frase graciosa que se plasma en aspectos mas concretos como que el programa se escriba pensando en que hay que probarlo.

5. Si en un módulo (o sección de un programa, en general) se encuentran muchos fallos, hay que insistir sobre él. Es muy habitual que los fallos se concentren en pequeñas zonas. Hay mil causas para que ocurra este efecto:

o código escrito por un programador malo o código muy difícil o código mal o insuficientemente especificado o código escrito en un mal día, con prisas, ...

Además, cuanto más se parchea un trozo de código, tanto más ruinoso queda y susceptible a derrumbamientos. A la larga hay que acabar tirándolo y empezando de nuevo.

6. Si se detecta un fallo aislado, puede bastar una corrección aislada. Pero si se detectan muchos fallos en un módulo, lo único práctico es desecharlo, diseñarlo de nuevo, y recodificarlo. La técnica de ir parcheando hasta que se pasan una serie de pruebas es absolutamente suicida y sólo digna del avestruz.

7. Las pruebas pueden encontrar fallos; pero jamás demostrar que no los hay.

Es como las bruxas: nadie las ha visto; pero haberlas, haylas.

Ningún programa (no trivial) se ha probado jamás al 100%.

8. Las pruebas también tienen fallos. Los errores son propios de los humanos: todo el mundo se equivoca. Si una prueba falla, hay que revisar tanto lo que se prueba como lo que lo prueba. No obstante, la experiencia muestra que (casi siempre) hay más fallos el probado que en el probador.

9. Conclusiones

Probar es buscarle los fallos a un programa.

La fase de pruebas absorbe una buena porción de los costes de desarrollo de software. Además, se muestra renuente a un tratamiento matemático o, simplemente, automatizado. Su ejecución se basa en metodología (reglas que se les dan a los encargados de probar) que se va desarrollando con la experiencia. Es tediosa, es un arte, es un trabajo que requiere una buena dosis de mala intención, y provoca difíciles reacciones humanas.

390

Page 391: Programacion en Java

Aunque se han desarrollado miles de herramientas de soporte de esta fase, todas han limitado su éxito a entornos muy concretos, frecuentemente sólo sirviendo para el producto para el que se desarrollaron. Sólo herramientas muy generales como analizadores de complejidad, sistemas de ejecución simbólica y medidores de cobertura han mostrado su utilidad en un marco más amplio. Pero al final sigue siendo imprescindible un artista humano que sepa manejarlas.

A. Bibliografia

1. Glenford J. MyersEl Arte de Probar el Software (The Art of Software Testing)El Ateneo, 1983 (John Wiley & Sons, Inc. 1979)

Es "el clásico" por antonomasia. Está muy bien escrito, claro y conciso. Sólo adolece de cierta vejez en cuanto los ejemplos se refieren a PL/I, y otras anticuallas.

2. Barbee Teasley MynattSoftware Engineering with Student Project GuidancePrentice-Hall International Editions, 1990

Es un libro muy pragmático, escrito por una sicóloga metida a ingeniera software. No se anda por las ramas.

3. Boris BeizerSoftware Testing TechniquesVan Nostrand Reinhold (N.Y.) 2a ed. 1990

Es como la biblia de las pruebas. Un libro quizás algo excesivo y sin duda exhaustivo sobre el tema.

4. Roger S. PressmanSoftware Engineering: A Practitioner's ApproachMcGraw-Hill Intl. Eds. 1987

No está mal, aunque quizás se enrolla un poco y no concreta.

La mayor parte de los libros tratan esta fase del desarrollo de programas de formas muy peculiares, con más rollo que ciencia y sin dejar claro lo que hay que hacer en un caso práctico. Es muy raro que los libros que se dedican a enseñar un lenguaje o a enseñar a programar traten seriamente este tema. Hay que ir necesariamente a libros de ingeniería software.

B. Dictionary

Aunque he intentado utilizar traducciones razonables e intuitivas de los términos mas habitulamente utilizados, es bien cierto que lo más frecuente es que en la práctica nos encontremos la literatura en inglés. Esta mini-diccionario intenta cubrir la terminología anglosajona.

acceptance testing pruebas de aceptación alpha testing pruebas a nivel alfa back-box testing pruebas de caja negra beta testing pruebas a nivel beta boundary testing pruebas de casos límite branch coverage cobertura de ramas

391

Page 392: Programacion en Java

conformance testing pruebas de homologación coverage cobertura debugging depuración decision coverage cobertura de decisiones desk checking pruebas de despacho dynamic testing pruebas dinámicas equivalence partitioning particiones de equivalencia error-prone modules módulos sospechosos functional tests pruebas funcionales hand execution ejecución manual incremental coding codificación incremental integration testing pruebas de integración interoperability testing pruebas de interoperabilidad loop coverage cobertura de bucles performance tests pruebas de prestaciones quality calidad regression testing pruebas de regresión robustness tests pruebas de robustez segment coverage cobertura de segmentos statement coverage cobertura de sentencias static testing pruebas estáticas stress tests pruebas de robustez structural tests pruebas estructurales test harness banco de pruebas testing pruebas testing in the large pruebas a escala industrial testing in the small pruebas a pequeña escala unit testing pruebas de unidades validation validación verification verificación white-box testing pruebas de caja blanca

C. Caso Práctico

Los ejemplos de pruebas de programas suelen irse a uno de dos extremos: o son triviales y no se aprende nada, o son tan enormes que resultan tediosos. El ejemplo elegido para esta sección pretende ser comedido, a costa de no contemplar mas que un reducido espectro de casos.

Nos dan para probar un procedimiento

PROCEDURE Busca (C: CHAR; V: ARRAY OF CHAR): BOOLEAN;

A este procedimiento se le proporciona un caracter C y un array V de caracteres. El ARRAY debe estar ordenado alfabéticamente, en orden ascendente. El procedimiento devuelve TRUE si C está en V, y FALSE si no. Trabajamos en Modula-2.

Lo primero que hay que hacer es identificar clases de equivalencia sobre su interfaz:

392

Page 393: Programacion en Java

• C: CHAREl parámetro C está muy poco especificado. Sólo se dice que es un caracter, lo que queda de lo más ambiguo pues esto significa conjuntos diferentes dependiendo del ordenador.

• V: ARRAY OF CHARNo se dice nada del conjunto de caracteres posibles (como en C), ni de las simensiones límite del ARRAY. Tampoco se dice nada del criterio de ordenación.

• resultado: BOOLEANÉste si está perfectamente claro.

Para probar algo necesitamos saber más. La única forma es tener una charla con el que especificó la función y aclarar estos extremos. Todas estas aclaraciones deben quedar recogidas por escrito en una nueva versión de la especificación:

A este procedimiento se le proporciona un caracter C y un array V de caracteres. Se admitirá cualquier caracter de 8 bits de los representables en un PC con Modula-2. El ARRAY podrá tener entre 0 y 10.000 caracteres y estar ordenado alfabéticamente, en orden ascendente. El orden de los caracteres es el proporcionado por el Modula-2 sobre el tipo CHAR. Es admisible cualquier cadena de caracteres construida según el convenio de Modula-2 para este tipo de datos. El procedimiento devuelve TRUE si C está en V, y FALSE si no. Trabajamos en Modula-2.

Con estas explicaciones identificamos las siguientes clases de equivalencia

• C: CHAR 1. Cualquier caracter

• V: ARRAY OF CHAR 1. El ARRAY vacio. 2. Un ARRAY entre 1 y 10.000 elementos, ambos inclusive, ordenado. 3. Un ARRAY entre 1 y 10.000 elementos, ambos inclusive, desordenado.

• resultado: BOOLEAN 1. TRUE 2. FALSE

Por último, cabe considerar combinaciones significativas de datos de entrada: que C sea el primero o el último del ARRAY.

1. Pruebas de caja negra: valores límite 1. Buscar el caracter 'k' en el ARRAY "" Debe devolver FALSE. 2. Buscar el caracter 'k' en el ARRAY "k" Debe devolver TRUE. 3. Buscar el caracter 'k' en el ARRAY "j" Debe devolver FALSE. 4. Buscar el caracter 'k' en el ARRAY "kl" Debe devolver TRUE. 5. Buscar el caracter 'k' en el ARRAY "jk" Debe devolver TRUE. 6. Buscar el caracter 'k' en el ARRAY de 10.000 "a" Debe devolver FALSE.

Vamos a olvidar de momento las posibles pruebas referentes a la ordenación del ARRAY.

2. Pruebas de caja negra: valores normales 1. Buscar el caracter 'k' en el ARRAY "abc" Debe devolver FALSE. 2. Buscar el caracter 'k' en el ARRAY "jkl" Debe devolver TRUE.

Para pasar a caja blanca necesitamos conocer el código interno:

393

Page 394: Programacion en Java

1 PROCEDURE Busca (C: CHAR; V: ARRAY OF CHAR): BOOLEAN; 2 VAR a, z, m: INTEGER; 3 BEGIN 4 a:= 0; 5 z:= Str.Length (V) -1; 6 WHILE (a <= z) DO 7 m:= (a+z) DIV 2; 8 IF V[m] = C THEN RETURN TRUE; 9 ELSIF V[m] < C THEN a:= m+1; 10 ELSE z:= m-1; 11 END; 12 END; 13 RETURN FALSE; 14 END Busca;

Es laborioso; pero si nos molestamos en ejecutar todas las pruebas anteriores marcando por dónde vamos pasando sobre el código, nos encontraremos con que hemos ejecutado todas las sentencias con excepción de la rama de la línea 10. Para atacar este caso necesitamos un caso de prueba adicional de caja blanca

3. Pruebas de caja blanca: 1. Buscar el caracter 'k' en el ARRAY "l" Debe devolver FALSE.

Con el conjunto de pruebas que llevamos hemos logrado una cobertura al 100% de segmentos y de condiciones. Respecto del bucle, la prueba 1.1 lo ejecuta 0 veces, y las demás pruebas 1 o más veces.

El conjunto de pruebas identificado se puede traducir en un banco de pruebas con el siguiente aspecto:

IF Busca ('k', "") THEN IO.WrStr ("falla 1.1"); END; IF NOT Busca ('k', "k") THEN IO.WrStr ("falla 1.2"); END; IF Busca ('k', "j") THEN IO.WrStr ("falla 1.3"); END; IF NOT Busca ('k', "kl") THEN IO.WrStr ("falla 1.4"); END; IF NOT Busca ('k', "jk") THEN IO.WrStr ("falla 1.5"); END; IF Busca ('k', aaaa) THEN IO.WrStr ("falla 1.6"); END; IF Busca ('k', "abc") THEN IO.WrStr ("falla 2.1"); END; IF NOT Busca ('k', "jkl") THEN IO.WrStr ("falla 2.2"); END; IF Busca ('k', "l") THEN IO.WrStr ("falla 3.1"); END;

Aún podríamos pasar algunas pruebas más para comprobar la solidez del programa. Concretamente, sería bueno considerar qué ocurre si sobrepasamos el tamaño máximo de 10.000 caracteres o si el ARRAY estuviera desordenado. La especificación del módulo no dice nada de esto, por lo que el análisis del resultado es vidrioso. Sobre el código concreto podemos apreciar que el tamaño del ARRAY puede llegar hasta el máximo entero soportable por la implementación de Modula-2 que estemos usando. Sobrepasado este límite se puede producir un error de asignación fuera de rango en la línea 5. Por otra parte, si el ARRAY está desordenado, el resultado es arbitrario, aunque la función siempre termina devolviendo TRUE o FALSE.

394

Page 395: Programacion en Java

Curso de Fortran

TEMA 1: ESTRUCTURAS Y ELEMENTOS DEL LENGUAJE FORTRAN.

CARACTERES DEL LENGUAJE FORTRAN.

-El lenguaje Fortran tiene unos números y signos que utiliza y que funcionan como caracteres o letras, siendo los caracteres permitidos por este lenguaje los siguientes:

-Letras de la A a la Z (Tanto mayúsculas como minúsculas).

-Números del 0 al 9.

-Caracteres de puntuación ., ;.

-Caracteres matemáticos +, *, /, -.

-Caracteres especiales $, , =, <, >, (), :, ´, y el blanco.

-El lenguaje Fortran no distingue en la sintaxis las letras mayúsculas y minúsculas salvo en el caso de los literales, y los signos de la comparación no los suele utilizar.

-Las nuevas versiones de Fortran usan todos los códigos relacionales pero con la excepción de que forman parte de los literales.

-Semántica, es lo que define el contenido de ciertas palabras y un conjunto de palabras reservadas cada una con un cometido especial. En este lenguaje hay un conjunto pequeño de palabras reservadas.

-No existen palabras reservadas como tal, sino palabras clave, sabiendo que cualquier palabra puede ser un identificador válido.

-Con objeto de dar nombre a las cosas que se manipulan (Módulo, ficheros estructuras, constantes, etc.) se establecen unas normas que son:

-El carácter de una palabra debe ser un carácter alfabético, que aparte de las 52 letras considera como caracteres alfabéticos los símbolos $ y .

-El número máximo de caracteres que se pueden utilizar son 31, aunque el compilador sólo reconoce los seis primeros.

-El nombre de los objetos debe tener un significado sobre el objeto que se está tratando (Que tenga sentido), se debe hacer una normalización de los nombres.

FORMATO DEL LENGUAJE FORTRAN.

-El lenguaje Fortran no está formateado. Las cinco primeras columnas son columnas reservadas para las etiquetas (Un número desde el uno hasta el 9) y referenciará una línea.

395

Page 396: Programacion en Java

-La etiqueta comprenderá un número del 1 al 99999 entero y sin signo, que deberá ser única y ocupará memoria.

-No tiene por qué estar ordenada pero no se puede poner una etiqueta más de una vez en un programa (Recomendable colocar la etiqueta para los formatos).

-La sexta columna irá en blanco y sólo se utiliza para indicar que la línea que se está tratando es una línea de continuación (Cualquier carácter es válido).

-Como máximo se pueden continuar 19 líneas de programa y hay que hacer una indicación.

-Las columnas 7 a 72 sirven para colocar el código fuente. Todas las sentencias se clasificarán en dos grupos:

-Ejecutables (Especifican la acción y generan una instrucción).

-No ejecutables (Información y naturaleza de los datos).

-Las columnas 73 a 80 sirven para realizar todo tipo de comentarios, entre los cuales pueden figurar:

-Nombre del programador.

-Versión del programa.

-Fecha del programa.

-Descripción del programa.

-E/S del programa.

-Variables y qué representan.

-La primera columna puede llevar un conjunto de caracteres con un significado especial que serán la c, C, * y el símbolo $.

-Los comentarios no pueden ir entre líneas de continuación pero pueden estar en cualquier parte del programa excepto la anteriormente citada.

TIPOS DE MODULOS.

-Para referenciar un programa principal en Fortran se utilizará el siguiente formato, teniendo en cuenta que dentro del programa principal irán las acciones a cumplir:

- PROGRAM Identificador

acción1

acción2

.......

396

Page 397: Programacion en Java

acciónn

END

-El especificador o identificador del programa puede ser omitido y no es necesario colocarlo dentro de un programa.

-FUNCTION Identificador (Parámetros)

acción1

acción2

.......

acciónn

END

-Se utilizará la estructura de Función cuando se necesite devolver o no un valor, teniendo en cuenta que la lista de los parámetros pasados deben ser separados por comas.

-SUBROUTINE Identificador (Parámetros)

acción1

acción2

.......

acciónn

END

-La Subrutina o Procedimiento se utilizará en aquellos casos en los que se necesite establecer una relación de más de un tipo de dato. La lista de parámetros pasados deben ser separados por comas.

-BLOCK DATA Identificador

acción1

acción2

.......

acciónn

END

397

Page 398: Programacion en Java

-Esta estructura representa un conjunto de instrucciones meramente descriptivas que serán datos comunes o aparte de los Subprogramas y se pueden inicializar.

-Estos cuatro elementos se pueden colocar a partir del programa principal o se pueden ejecutar y compilar separadamente.

-El lenguaje no necesita que una variable esté declarada anteriormente sino que tienen una declaración implícita (Lo distingue por la primera letra).

TIPOS DE DATOS.

-Como norma general el lenguaje no utiliza tipos de datos.

DATOS DE TIPO ENTERO.

-Son todas las variables que comiencen por una letra que esté en el intervalo I-N, serán variables tomadas como enteras si no se declara explícitamente de otro tipo o también se pueden declarar como uno de

los siguientes:

-INTEGER *1 (Asigna al entero un byte de longitud).

-INTEGER *2 (Asigna al entero dos bytes de longitud).

-INTEGER *4 (Asigna al entero cuatro bytes de longitud).

-Si después de la palabra reservada INTEGER no se pone nada la longitud por defecto de la variable declarada serán 4 bytes.

-El separador usado suele ser la coma y para definir varias variables de tipo entero se pueden usar los siguientes formatos:

-INTEGER *1 Cont, Cont1, Cont2 (Declara 3 variables enteras).

-Signo 2..36 0..9 ó A..Z .

- Número (Indica base 16 y es perfectamente válido).

-El segundo formato define un número en otra base que no sea base decimal, siendo el símbolo el que indica la base en la que se va ha representar el número.

DATOS DE TIPO REAL.

-Es el número manipulado por excelencia en Fortran. Hay dos operaciones de tipo general que son la coma fija y la coma flotante.

-Los formatos que utilizan los números reales serán:

-Signo parte entera. parte fraccionaria (C. fija).

398

Page 399: Programacion en Java

-Signo parte entera E signo parte fraccionaria (C. flotante)

-Para declarar una variable de tipo real se usará uno de los siguientes formatos:

-REAL *4 (Asigna al real cuatro bytes de longitud).

-REAL *8 (Asigna al real ocho bytes de longitud).

-El error que se comete al usar cuatro bytes de longitud se producirá en las siete u ocho cifras después de la coma.

-Cuando se utiliza un formato de cuatro bytes estamos ante un número de simple precisión.

-El real declarado como ocho bytes abarca después de la coma 14 o 15

cifras significativas para la parte entera del número que es aproximadamente el doble del formato de cuatro bytes.

-Cuando se utiliza un formato de ocho bytes estamos ante un número de doble precisión.

DATOS DE TIPO COMPLEJO.

-Este tipo de datos consta de una parte real y de una parte que es imaginaria y en su representación se asumirá la parte real y se quedará con la parte imaginaria.

-Los formatos que se utilizan para los números complejos serán:

-Complejo (Real, Imaginario).

-Para declarar una variable de tipo complejo se usarán uno de los siguientes formatos:

-COMPLEX *4 (Asigna al complejo cuatro bytes de longitud).

-COMPLEX *8 (Asigna al complejo ocho bytes de longitud).

-Después de la palabra reservada COMPLEX puede aparecer un signo, y la parte real e imaginaria irán entre comas).

-Al igual que en otros tipos de datos cuatro bytes serán simple precisión y ocho bytes serán doble precisión.

DATOS DE TIPO CARACTER.

-Estas variables se representan por la palabra reservada CHARACTER y el formato que utiliza Fortran para definir las variables de este tipo son:

-CHARACTER *Número .

-En este formato, número nos indica la longitud en caracteres de la variable, siendo el tamaño máximo que puede tomar un campo carácter desde 256 hasta 32767 caracteres.

399

Page 400: Programacion en Java

-Este formato define el tratamiento de cadenas.

DATOS DE TIPO LOGICO.

-Las variables de tipo lógico se representan mediante la palabra reservada LOGICAL con las variables separadas por comas y con un formato como el siguiente:

-LOGICAL *2 (Asigna al campo dos bytes de longitud).

-LOGICAL *4 (ASigna al campo cuatro bytes de longitud).

-Los valores que puede tomar una variable de tipo lógico son los típicos de True y False.

DATOS DE TIPO REGISTRO.

-Para este tipo de variables tenemos que definir previamente una estructura y sus correspondientes campos que se van ha tomar, siendo dicha estructura del tipo:

-STRUCTURE Identificador

CHARACTER *25 Campo1

INTEGER Campo2

REAL Campo3

LOGICAL Campo4

.....................

END STRUCTURE

-La implementación del registro que podría usarse con la estructura representada podría ser la siguiente:

-RECORD Identificador1 Identificador2 (Número).

-Donde identificador1 representa el nombre de la estructura creada, identificador2 será el nombre del registro y Número será el número de elemento del registro.

-Si por ejemplo deseamos referenciar a un real del registro número 25 tendremos que utilizar la siguiente forma:

-Identificador2 (25).Campo3.

CONSTANTES.

-Para definir una constante en Fortran deberemos utilizar el siguiente formato:

-PARAMETER (Identificador=constante, ...).

400

Page 401: Programacion en Java

-La lista de constantes irá separada por comas. Las constantes podrán ser de tipo numérico o de tipo Hollerith (Cadena de caracteres con cualquier carácter imprimible). También podrán ser expresiones.

-Las constantes numéricas podrán tener los siguientes formatos:

-Sin punto decimal ni coma.

-Precedida por un signo, en los enteros.

-Coma fija con punto decimal, en los reales.

-Coma flotante con exponente entero, en los reales.

-Simple precisión (n.m, n., .m, n.mEe, n.E+e, nE+e).

-Doble precisión (n.mD+e, n.De, mDe, nDe).

-Compleja como un par ordenado entre paréntesis.

-Lógicas .TRUE. y .FALSE..

-De caracteres.

-La constante Hollerith podrá tener el siguiente formato:

-Número H carácter.

-Una constante simbólica definida en PARAMETER puede aparecer como una expresión o como un valor de DATA.

VARIABLES.

-Para definir una variable el compilador conoce ya de antemano si la variable va a ser de tipo real o de tipo entera, siendo por defecto de tipo real.

-La estructura que deberán seguir las declaraciones de las variables en un programa Fortran será la siguiente:

-PROGRAM Identificador

PARAMETER (Lista de constantes)

*VARIABLES

INTEGER Lista de variables

REAL Lista de variables

COMPLEX Lista de variables

401

Page 402: Programacion en Java

CHARACTER Lista de variables

LOGICAL Lista de variables

RECORD Tipos

*INICIO

.......

-Un atributo es una palabra reservada que acompaña a la variable y sirve para que el programa realice un mejor aprovechamiento de memoria, mejor dimensionamiento de arrays dinámicos, etc.

-Si la variable comienza con una letra que se encuentre en el intervalo I-N se tratará de una variable de tipo entera. Análogamente si comienza con una letra del intervalo A-H, O-Z se tratará de una variable real.

-Como norma general, una variable debe ser declarada antes de que pueda utilizarse. Para inicializar una lista de variables a determinados valores se usará el siguiente formato:

-DATA Lista de variables, lista de constantes.

-Esta sentencia debe preceder a cualquier sentencia ejecutable. Una constante seguida de un asterisco indicará repetición.

EXPRESIONES.

-En las expresiones aritméticas sabemos que si se realiza una operación de un entero con un real, siempre se codificará el entero como real y luego se realiza la operación.

-Se convierte el tipo que contiene al otro, realizándose siempre con la máxima precisión. Los operandos que se van a usar en las expresiones aritméticas serán los siguientes:

-** (Operador potencia).

-/ (Operador división real).

-* (Operador producto).

-- (Operador resta).

-+ (Operador suma).

-() (Paréntesis).

-Para el uso de operadores relacionales, Fortran no admite como unos operadores relacionales los signos siguientes:

-<=, >=, <, >, <>.

-Los operadores relacionales que están permitidos en este lenguaje serán los siguientes:

402

Page 403: Programacion en Java

-.LT. (Operador menor que).

-.LE. (Operador menor o igual que).

-.NE. (Operador distinto de).

-.GT. (Operador mayor que).

-.GE. (Operador mayor o igual que).

-.EQ. (Operador igual que).

-Una expresión relacional compara los valores de dos expresiones de tipo aritmético o de caracteres y el resultado es de tipo lógico. Los formatos que puede tomar una sentencia relacional serán:

-Variable operador variable.

-Variable operador constante.

-Las variables no han de ser necesariamente variables, también pueden ser expresiones de todo tipo.

-En cuanto a los operadores lógicos que están permitidos por Fortran se encuentran los siguientes:

-.NOT. (Operador negación).

-.AND. (Operador producto lógico).

-.OR. (Operador suma lógica).

-.XOR. (Operador suma exclusiva lógica).

-.EQV. (Operador de equivalencia lógica).

-.NEQV. (Operador de no equivalencia lógica).

-El resultado de la evaluación de una expresión de tipo lógico será un valor de tipo lógico, pudiendo ser el formato de la expresión:

-Operando1 operador operando2.

-El orden de prioridad establecido para la creación de variables en lenguaje Fortran será por tanto el siguiente:

-Expresiones aritméticas.

-Expresiones de caracteres.

-Expresiones relacionales.

403

Page 404: Programacion en Java

-Expresiones lógicas.

-Para la declaración de variables se utiliza la palabra reservada IMPLICIT, que toma una variable cualquiera a no ser que se indique lo contrario. El formato que utiliza la instrucción es el siguiente:

-IMPLICIT REAL (A-Z).

-El formato anterior indica que todas las variables que empiecen desde la a hasta la z sean tratadas como reales de forma implícita. Una variable suele ser tratada siempre como un real.

FINAL DE UN PROGRAMA.

-Hay dos formas de terminar la ejecución de un programa, lógicamente o físicamente.

-Para terminar la ejecución de un programa lógicamente se utiliza el siguiente formato:

-STOP Número.

-Número es la constante de caracteres o constante entera sin signo de cinco dígitos, o puede ser un mensaje de error encerrado entre dos comillas.

-Para terminar la ejecución de un programa físicamente se utiliza el siguiente formato:

-END.

ENTRADA Y SALIDA DE DATOS.

-Para la introducción de datos se utiliza la palabra reservada READ con el formato siguiente:

-READ (Número, Etiqueta).

-Donde número es el número que indica la unidad de entrada, colocando el valor * si no se quiere poner nada. Etiqueta indica la etiqueta que va ha definir un formato, si no se quiere poner nada se usa el valor *.

-Para la lectura de elementos con READ las variables se separan por un blanco o por comas, si se ha de introducir una frase o algo de tipo carácter, irá encerrado entre comillas.

-Si no hay suficientes datos para esta instrucción se produce un error y el programa aborta.

-Para la salida de datos se utilizan dos métodos igualmente válidos, que son dos palabras reservadas y que tienen el siguiente formato:

-PRINT *, Lista de variables (Separadas por comas).

-WRITE (*,*), Lista de variables (Separadas por comas).

-La lista de variables pueden ser tanto variables, como constantes como expresiones de todo tipo. El cursor avanza a la siguiente línea. Este tipo de sentencias se suelen incluir antes de una sentencia READ.

404

Page 405: Programacion en Java

TEMA 2: FUNCIONES INTRINSECAS.

CONCEPTO DE FUNCION.

-Son aquellas funciones incorporadas al compilador, que son accesibles desde un programa. Pueden ser llamadas en un programa dando el identificador de la Función seguido por sus argumentos entre paréntesis.

-Estas funciones pueden tener uno o varios argumentos y se caracterizan porque:

-El nombre y sus valores de entrada son uno mismo.

-Nunca puede ser usada en el lado izquierdo de la asignación.

-El nombre determina el tipo de salida de la Función.

-Los argumentos son del mismo tipo que la Función.

-Los argumentos pueden ser expresiones de todo tipo.

-El formato de la Función será el siguiente:

-Nombre (Identificador1, identificador2, ..., identificadorn).

FUNCIONES ARITMETICAS.

-Son aquellas que realizan algún tipo de operación matemática.

-Calcula el valor absoluto de un argumento real y devuelve un resultado real y positivo:

-ABS (Expresión numérica).

-Convierte un real a un entero truncándolo o eliminando la parte decimal devuelve un entero:

-Var=INT (Expresión numérica).

-INT (10**VarN*VarX)/10**VarN (N son los decimales a truncar).

-INT (10**VarN*VarX+0.5)/10**VarN (N son los decimales deseados).

-NINT (Expresión numérica) (Realiza un redondeo).

-Convierte un número entero a un real:

-FLOAT (Expresión numérica).

-Eleva un número real a la enésima potencia donde e es la base del logaritmo natural, devuelve un real:

-EXP (Número).

405

Page 406: Programacion en Java

-Var=EXP (Número).

-DEXP (Número) (Donde número es de doble precisión).

-CEXP (Número) (Donde número es un número complejo).

-Calcula el logaritmo natural de base e y es la inversa de la Función anterior, devuelve un real:

-Var=LOG (Expresión numérica).

-Calcula el logaritmo decimal o de base diez del argumento indicado, devuelve un real:

-Var=LOG10 (Expresión numérica).

-Calcula la raíz cuadrada del argumento indicado, devuelve un real:

-Var=SQRT (Expresión numérica).

-Devuelve el mayor valor de una serie de argumentos indicados:

-MAX0 (Lista de identificadores) (Devuelve un entero).

-AMAX1 (Lista de identificadores) (Devuelve un real).

-DMAX1 (Lista de identificadores) (Devuelve un doble precisión).

-Devuelve el menor valor de una serie de argumentos indicados:

-MIN0 (Lista de identificadores) (Devuelve un entero).

-AMIN1 (Lista de identificadores) (Devuelve un real).

-DMIN1 (Lista de identificadores) (Devuelve un doble precisión).

-Calcula el módulo o resto de dos números, devuelve un entero:

-MOD (Número1, número2).

-Transfiere el signo del segundo identificador al primero:

-SIGN (Expresión1, expresión2).

-Convierte un número de doble precisión a un número real:

-SNGL (Número).

FUNCIONES TRIGONOMETRICAS.

-Son aquellas que realizan alguna operación con las razones de tipo trigonométrico.

-Calcula el seno de una expresión dada, devuelve un real y debe estar entre 1 y -1:

406

Page 407: Programacion en Java

-Var=SIN (Expresión numérica) (Devuelve un real).

-DSIN (Expresión numérica) (Devuelve un doble precisión).

-CSIN (Expresión numérica) (Devuelve un complejo).

-Calcula el coseno de una expresión dada, devuelve un real y debe estar entre 1 y -1:

-Var=COS (Expresión numérica) (Devuelve un real).

-DCOS (Expresión numérica) (Devuelve un doble precisión).

-CCOS (Expresión numérica) (Devuelve un complejo).

-Calcula la tangente de una expresión dada, devuelve un real:

-Var=TAN (Expresión numérica) (Devuelve un real).

-DTAN (Expresión numérica) (Devuelve un doble precisión).

-Calcula el arcoseno de una expresión dada, devuelve un real:

-Var=ASIN (Expresión numérica).

-Calcula el arcocoseno de una expresión dada, devuelve un real:

-Var=ACOS (Expresión numérica).

-Calcula el arcotangente de una expresión dada, devuelve un real:

-Var=ATAN (Expresión numérica).

-Calcula el seno hiperbólico de una expresión dada, devuelve un real:

-Var=SINH (Expresión numérica).

-Calcula el coseno hiperbólico de una expresión dada, devuelve un real:

-Var=COSH (Expresión numérica).

-Calcula la tangente hiperbólico de una expresión dada, devuelve un real:

-Var=TANH (Expresión numérica).

FUNCIONES DE NUMEROS COMPLEJOS.

-Realiza diferentes funciones con los números complejos.

-Toma la parte real de un argumento complejo:

-REAL (Expresión compleja).

407

Page 408: Programacion en Java

-Toma la parte imaginaria de un argumento complejo:

-AIMAG (Expresión compleja).

-Calcula el conjugado de un argumento complejo:

-CONJ (Expresión compleja).

-Representa un argumento complejo tomado de números reales:

-COMPLEX (Número1, número2).

TEMA 3: ESTRUCTURAS DE CONTROL SELECTIVAS Y REPETITIVAS.

ESTRUCTURAS DE BIFURCACION INCONDICIONAL.

-Este tipo de estructuras son unas herramientas que sirven para confeccionar las estructuras selectivas. No son recomendables y sólo se mantienen por compatibilidad con otras versiones.

-La siguiente estructura es un IF aritmético, en el que se realiza la elección de tres posibles etiquetas. Se evalúa la condición y en Función de esa condición se tomarán los siguientes valores:

-Condición <0 toma Etiqueta1.

-Condición =0 toma Etiqueta2.

-Condición >0 toma Etiqueta3.

-En este formato se pueden colocar dos etiquetas iguales, siendo el formato de la instrucción:

-IF (Expresión aritmética) Etiqueta1, Etiqueta2, Etiqueta3.

-La siguiente instrucción se usará en el caso de que haya sentencias repetitivas que no estén implementadas en el compilador, siendo su misión la de transferir el control a la sentencia especicada en la etiqueta:

-GOTO Etiqueta.

-La siguiente instrucción transfiere el control a la etiqueta enésima de la lista según un valor entero. Es el GOTO calculado.

-En esta sentencia se evalúa la expresión entera entre uno y el número de etiquetas, y se bifurcará a la etiqueta indicada para ejecutar un conjunto de sentencias según la expresión entera. Su formato es:

-GOTO (Etiqueta1, etiqueta2, ..., etiquetan) Expresión entera.

-La instrucción GOTO asignado tiene como formato el siguiente:

-ASSIGN Constante entera TO Variable entera.

408

Page 409: Programacion en Java

-GOTO Variable entera (Etiqueta1, etiqueta2, ..., etiquetan).

-Esta instrucción produce una estructura multibifurcación. La variable debe tener una de las etiquetas de GOTO y su funcionamiento es igual que la anterior.

-Para llevar la variable a la etiqueta se usa la opción de ASSIGN. Si la variable no está asignada el GOTO no es válido.

ESTRUCTURAS ALTERNATIVAS.

-Dentro de las estructuras alternativas hay tres tipos.

-La estructura alternativa simple, realiza un conjunto de acciones si la condición es verdadera y sigue el flujo de control secuencial si la condición es falsa.

-En las estructuras alternativas todas las condiciones se colocan siempre entre paréntesis. El formato de la alternativa simple es:

-IF (Expresión lógica) Sentencia.

-La expresión lógica está formada por una expresión con relacionales y la sentencia a continuación no pueden ser las palabras reservadas DO, ELSEIF, ELSE, ENDIF, END o IF.

-La estructura alternativa doble, selecciona una de dos opciones. Si la condición es verdadera se ejecutan unas acciones y si es falsa otras acciones. El formato de esta estructura sería:

-IF (Condición) THEN

acción1

acción2

.......

acciónn

ENDIF

-Esta última estructura se puede anidar con varias sentencias IF, y su estructura quedaría:

-IF (Condición) THEN

acción1

acción2

.......

acciónn

ELSE

409

Page 410: Programacion en Java

acciónn+1

acciónn+2

.........

acciónn+n

..............

ENDIF

-Si se quieren anidar varios niveles de sentencias IF-ELSE usaremos la siguiente estructura:

-IF (Condición) THEN

acción1

acción2

.......

acciónn

ELSEIF (Condición2) THEN

acciónn+1

acciónn+2

.........

acciónn+n

....................

ELSE

acciónm+1

acciónm+2

.........

acciónm+n

ENDIF

-La estructura alternativa múltiple se utiliza cuando la condición tiene más de dos valores con selección múltiple. El valor de su expresión a de ser un entero, carácter o lógico.

410

Page 411: Programacion en Java

-Si el carácter utilizado es numérico no irá entre comillas, mientras que si es de tipo carácter irá entre comillas. Esta última medida exige que el código ASCII del primer carácter sea menor que el segundo.

-El formato de la estructura será el siguiente:

-SELECT CASE (Expresión)

CASE (Caso1)

acción1

acción2

.......

acciónn

CASE (Caso2)

acciónn+1

acciónn+2

.........

acciónn+n

...............

CASE DEFAULT

acciónm+1

acciónm+2

.........

acciónm+n

END SELECT

CONCEPTO DE BUCLE.

-Consiste en una estructura de control que gobierna los procesos de tipo repetitivo dentro de un programa. Repite una secuencia de acciones mientras o hasta una condición sea verdadera o falsa.

-Iteración de un bucle es la repetición de sentencias interiores que hay dentro de un bucle.

ESTRUCTURA REPETITIVA. SENTENCIA "DO".

411

Page 412: Programacion en Java

-Esta estructura contiene dos formatos. El primer formato se utiliza cuando se conoce el número de iteraciones y su formato es:

-DO Etiqueta VarA=Inicio, Final, Incremento .

-Las variables se separan por comas y el incremento puede ser positivo o negativo y es opcional. Normalmente inicio debe ser menor que la variable final.

-El otro formato es lo mismo que una estructura de tipo repetir. Se usa cuando se desea repetir una condición un número de veces siendo una por defecto. Su formato es:

-DO Etiqueta VarA=Inicio, Final, Incremento

acción1

acción2

.......

acciónn

Etiqueta CONTINUE

ENDDO

-Las variables pueden ser enteras, constantes o expresiones, y es más rápida y potente con variables y constantes con lo que se evitan los errores de redondeo.

-Como mínimo se ejecuta una vez. Ejecuta un conjunto de instrucciones hasta que se encuentra a la etiqueta, y si no se usa la etiqueta se deberá cerrar el bucle con ENDDO.

-Por defecto el incremento del bucle es uno. Si se usa la etiqueta habrá que tener en cuenta una serie de restricciones:

-No se puede usar un GOTO incondicional o asignado.

-No se puede usar ELSE, IF, SELECT CASE, ENDIF.

-No puede seguirle un ENDSELECT, EXIT, RETURN o STOP.

-Para evitar estas restricciones bastará con colocar una sentencia en blanco. Si se quiere usar una variable índice después de salir la variable índice la variable tendrá el valor final más el incremento.

ESTRUCTURA REPETITIVA. SENTENCIA "WHILE".

-Utiliza una condición que puede ser una expresión lógica o relacional. Esta condición se evalúa antes y después de cada ejecución del bucle.

Si la condición es falsa no se ejecuta nunca.

-Los bucles deben terminar siempre y pueden ser controlados por:

412

Page 413: Programacion en Java

-Contador, necesita una variable dentro del bucle que se debe inicializar, comprobar e incrementar.

-Centinela, es un valor especial para el final de una lista y es un valor que jamás se procesa, pero debe ser del mismo tipo que los datos.

-Interruptor, es una variable lógica.

-El formato de la instrucción será la siguiente:

-DO Etiqueta WHILE (Expresión lógica)

acción1

acción2

.......

acciónn

Etiqueta CONTINUE

ENDDO

-Todas las restricciones anteriormente indicadas para la otra estructura repetitiva sirven para DO WHILE.

-Si por cualquier circunstancia el compilador no reconoce este tipo de estructuras se podrá implementar de la siguiente:

-Etiqueta IF (Condición) THEN

acción1

acción2

.......

acciónn

GOTO Etiqueta

ENDIF

-Hay que tener en cuenta que GOTO sólo se usará en el caso de este tipo de circunstancias y sólamente para ellas.

-Cuando se usen etiquetas, una sóla etiqueta valdrá para cerrar varios bucles que estén anidados. Pero si se usan ENDDO hay que colocar tantos como DO hayan.

TEMA 4: ENTRADAS Y SALIDAS CON FORMATOS.

413

Page 414: Programacion en Java

INSTRUCCION CON FORMATO DE ENTRADA. SENTENCIA "READ".

-Este tipo de instrucciones no es muy potente porque sólo están pensadas para introducir y sacar datos. La unidad estándar en el teclado está denominado como un asterisco (*).

-La lectura mediante la instrucción READ se realiza con la siguiente sintaxis:

-READ (Lista de descriptores), Lista de variables.

-La Lista de variables siempre irá separada por comas.

-Con esta sintaxis la lectura se realiza del dispositivo predefinido que puede ser uno de los siguientes:

-Constante entera (Que referencia otra sentencia).

-Variable carácter.

-Array de caracteres.

-Expresión carácter (Entre paréntesis y apóstrofes).

-Variable entera (Con una etiqueta FORMAT).

-Dentro de la lista de descriptores podemos encontrarnos las siguientes opciones:

-Unidad, que sirve para que a cada dispositivo se le asigne una unidad o un entero (Sin signo) y la palabra UNIT es opcional. Si no se coloca UNIT debe ir en primer lugar, si se pone la palabra reservada UNIT podrá ir en otro lugar. Su sintaxis es:

- UNIT= Entero.

-Formato, en el que podrán colocarse los siguientes parámetros:

-Etiqueta, asignada a la sentencia especial que será FORMAT.

-Variable, entera a la que previamente se le halla asignado una Etiqueta válida.

-Expresión, de tipo cadena que contenga los códigos de los formateos entre paréntesis que estarán cerrados entre unas comillas.

-Variable o array, de caracteres a los cuales se le hallan asignado los códigos de formato.

-Asterisco (*), que indicará una entrada sin formato.

sabiendo que el formato estará definido por la sintaxis:

- FMT= Formato.

-Número de registro, que sólo se usará en el tratamiento de los archivos de acceso directo, donde se le indica que lea el número relativo al registro siendo su estructura o sintaxis:

414

Page 415: Programacion en Java

-REC=Número registro.

-Código chequeador, de la operación al cual deposita el resultado de analizar la operación siendo su sintaxis:

-IOSTAT=Variable entera.

-Control del fin de archivo, que se especifica con la sintaxis:

-END=Etiqueta.

-Transferencia de control, que se produce cuando hay un error y que contiene la siguiente sintaxis:

-ERR=Etiqueta.

-Si se admiten UNIT y FMT, sus correspondientes valores deberán estar en el primer y segundo lugar de la instrucción READ respectivamente.

-Los dos primeros formatos se han de usar obligatoriamente, mientras que los otros parámetros sólo se usarán para ficheros externos. Un ejemplo de uso de esta sentencia sería:

-READ (UNIT=3, FMT=20, REC=10, IOSTAT=Cod, END=100, ERR=Error).

-Si la lectura es desde teclado el fin de fichero debe ser tecleado por el usuario con la combinación CTRL+Z.

INSTRUCCION CON FORMATO DE SALIDA. SENTENCIA "WRITE".

-Para este tipo de instrucciones nos encontramos dos formatos que están diferenciados:

-PRINT (Lista de descriptores), Lista de salida.

-WRITE (Lista de descriptores), Lista de salida.

-El primer formato siempre se utilizará cuando el dispositivo de salida esté predefinido por el sistema, mientras que el segundo formato se usará para una salida a una unidad específica.

-La lista de descriptores que se podrá usar serán los mismos que en la instrucción READ. UNIT y FMT podrán no figurar si se pone primero el número de la unidad y luego el formato.

INSTRUCCION DE FORMATOS. SENTENCIA "FORMAT".

-Esta instrucción facilita la información necesaria para que se haga un reconocimiento de la representación que los datos van a tomar en la memoria principal. La sintaxis de FORMAT será la siguiente:

-Etiqueta FORMAT (Lista de código de formato).

-Etiqueta será el número de etiqueta (Obligatorio), aunque se podrá referenciar también con una variable entera a la cual se ha asignado una etiqueta con ASSIGN.

415

Page 416: Programacion en Java

-La lista de códigos puede estar vacía si la lista de sentencias de E/S está vacía con un salto de línea.

-Esta sentencia no es ejecutable, puede escribirse en cualquier parte y conviene agruparlas todas al principio o al final del programa.

-La Lista de código de formato podrá contener uno o varios de los siguientes tipos:

-Códigos numéricos o de datos (Repetibles) entre los cuales tenemos:

-Código I.

-Código F.

-Código E.

-Código D.

-Código G.

-Código P.

-Código L.

-Código A.

-Códigos de posicionamiento (No repetibles) entre los cuales tenemos:

-Código X.

-Códigos T, Tl, Tr.

-Código /.

-Códigos especiales (No repetibles) entre los cuales tenemos:

-Códigos S, Sp, Ss.

-Códigos Bn, Bz.

-Código H.

-Código :.

-Código .

-Códigos de control de carro.

CODIGO NUMERICO "I".

416

Page 417: Programacion en Java

-En los códigos de datos o numéricos siempre hay que diferenciar tres partes que son fundamentales:

-Tipo de dato a representar.

-Tamaño para la representación.

-Puntos y tamaños de la parte fraccionaria.

-Entre los códigos numéricos existen una serie de características:

-Los blancos de relleno son ignorados.

-Si el signo es negativo se genera el signo -.

-Tiene prioridad el punto decimal de entrada ante la posición decimal especificada.

-Los campos se ajustan a la derecha.

-Si se produce error de salida salen asteriscos.

-Para la entrada de este código los datos deben ser de tipo entero, siendo I el identificador de código y w el ancho del campo y la sintaxis:

-Iw.

-Transfiere datos enteros desde el soporte externo a variables, tantos caracteres como valor tenga w contando dígitos, blancos y signos y asocia variables de izquierda a derecha con las normas siguientes:

-Blancos entre dígitos o al final se interpretan como ceros.

-No usan código de formato cuando la lectura es por teclado.

-Con Bn los blancos se ignoran y con Bz se toman como ceros.

-Para la salida los datos van desde la memoria interna al soporte externo con un valor w de ancho ajustándose a la derecha y si es negativo se coloca un signo delante del dígito más significativo.

-Si el número de caracteres es mayor que el ancho de salida el campo de salida se llena de asteriscos.

-Si se utiliza un coeficiente de repetición n deberá ser una constante entera sin signo mayor que cero con la sintaxis:

-nIw.

-Si se quiere indicar el número mínimo de dígitos que aparecerán en la salida se usará m, una constante entera sin signo con la sintaxis:

-nIw.m.

417

Page 418: Programacion en Java

-Se debe contemplar la opción de que el número sea negativo con lo que se deberá aumentar el formato en una unidad.

-Si en la salida se colocan menos dígitos que en el formato indicado se suprimirán todos los ceros que no sean significativos.

CODIGO NUMERICO "F".

-Este código transfiere datos de tipo real desde un dispositivo externo hasta la memoria o a la inversa, siendo su sintaxis la siguiente:

-Fw.d.

-En este código, F será el carácter del código, w será la longitud total del campo incluyendo los blancos, signos, puntos y dígitos, y d será el número de decimales de la parte real.

-Si no se introduce un punto decimal se sitúan los decimales de forma automática. El código F permite ser un código repetible con lo que su sintaxis será la siguiente:

-nFw.d.

-Se pueden introducir números reales en notación exponencial, siendo el exponente que sigue al número real de dos formas:

-Constante entera con signo + o -.

-Carácter E o D seguido de un signo y una constante entera.

-Los caracteres de las variables reales se ajustan a la derecha del campo de anchura. El punto decimal se genera con d posiciones a la derecha.

-En la salida no se producen ceros a la izquierda a no ser que la mantisa sea menor que uno, con lo que habrá un cero a la izquierda del punto decimal.

-Si la parte fraccionaria tiene más anchura que d habrá un redondeo del valor antes de ajustarse y si el dato no cabe en w se producirá una salida de w asteriscos.

-Entre los ejemplos que se pueden citar para este formato estarán:

CODIGO NUMERICO "E".

-Este formato lee o escribe datos reales en simple o doble precisión en notación exponencial. E es el código del exponente, w el ancho del campo y d los dígitos decimales con una sintaxis del tipo:

-Ew.d.

-La opción w contará la mantisa, el dígito que precede al punto decimal el punto decimal de la mantisa y el exponente que tendrá tres dígitos de forma que la representación será del tipo:

-(-)0.E(-)nn.

-Si el punto no figura en la entrada, la parte decimal serán los d

418

Page 419: Programacion en Java

dígitos más a la derecha de la mantisa. Si aparece el punto decimal

en la entrada no se tendrá en cuenta la especificación d en el formato.

-El signo del exponente se omite si es positivo y E o D pueden omitirse si el exponente tiene signo. Es recomendable especificar el punto decimal en la entrada.

-Para la salida han de reservarse una posición para el punto decimal y el dígito cero que precede al punto decimal si hay posición para él. Si la mantisa es negativa otra posición y el exponente cuatro posiciones.

-Se ejecuta la anchura del campo a la derecha y si w es menor que las posiciones para la salida se muestran asteriscos.

-En Función del desplazamiento del punto decimal se calcula el exponente y la mantisa se puede redondear, pudiéndose omitir el cero antecedente al punto decimal si no hay posición.

-El código E puede ser repetible con la siguiente sintaxis:

-nEw.d.

-Otro formato en el que una constante entera e nos indica el número de dígitos del exponente sería:

-Ew.dEe.

-Entre los ejemplos que se pueden citar para este formato estarán:

CODIGO NUMERICO "D".

-Describe números reales en simple o doble precisión en forma exponencial con el mismo efecto que el código E siendo su sintaxis:

-Dw.d.

-Para la entrada de datos este código es tratado de igual manera que el código E. Normalmente se utiliza este código para enfatizar que el número que se trata es de doble precisión.

-El exponente para la salida cambia la letra E por la letra D y tiene la siguiente sintaxis:

-D(+/-)nn.

-Este código numérico es repetible siendo su sintaxis:

-nDw.d.

CODIGO NUMERICO "G".

-Se utiliza este código para entrada y salida de datos de simple y doble precisión siendo sus sintaxis más generales las siguientes:

419

Page 420: Programacion en Java

-Gw.d.

-Gw.dEe.

-La entrada de datos reales tiene el mismo significado que los códigos

F, E y D.

-En la salida actúa como formato F cuando el valor de salida está en el rango entre 0.1 y 10**d, mientras que actúa como formato E cuando el número es más pequeño de 0.1 o mayor que 10**d.

-Si actúa como formato F entonces los últimos cuatro caracteres serán blancos y el valor se imprimirá en un ancho de campo de w-4 caracteres.

-Si se usan formatos a la entrada y se coloca un punto decimal cuando el formato es distinto, habrá disyunción entre el formato del teclado y el establecido en el programa.

-Siempre se da prioridad al formato introducido por teclado.

-Entre los ejemplos que se pueden citar para este formato estarán:

CODIGO DE DATOS "P".

-Es un factor de escala y sirve para ver los valores exponenciales con un entero delante del punto decimal, aplicable sólamente a los códigos F, E, D y G, afectando a la entrada de datos y a su salida.

-El valor n será un entero que puede llevar signo y su sintaxis será:

-nP.

-Al aplicar el código P en la entrada sólo es aplicable si el valor externo carece de exponente con lo que se expresará mediante la relación:

-Valor interno=(Valor externo)/10n.

-El factor de escala afecta a los códigos de formato que aparecen a continuación hasta encontrar otro factor de escala.

-Cuando se usa para el código F en la salida se produce desplazamiento del punto en el valor verdadero siendo su relación:

-Valor externo=Valor interno*10n.

-Si se usa en concexión con D o E la salida no cambia pero se desplaza el punto de la mantisa al hacer el producto por 10**n, y el exponente se decrementa en n, al igual que con la forma mantisa-exponente.

-Con el formato G no se usa el factor de escala porque da problemas.

-Si tenemos las siguientes instrucciones:

420

Page 421: Programacion en Java

-100 FORMAT (2PE7.2, F6.3, -1P8.4)

READ (5, 100) VarA, VarB, VarC

WRITE (*, '(1X, 3F12.5)') VarA, VARB, VarC

y le introducimos la siguiente entrada:

-b17.E0157.132-453261.7.

tendremos la siguiente tabla de referencia:

CODIGO DE DATOS "L".

-Este código se usa para la edición de datos lógicos siendo w el ancho del campo y siendo su sintaxis la siguiente:

-Lw.

-Para la entrada busca en el campo de forma que si el primer carácter es T (True) o F (False) los demás caracteres del campo son ignorados.

-Para la salida se produce una conversión de TRUE a FALSE o T a F ajustándose a la derecha del campo.

-El código L es un código de tipo repetible siendo su sintaxis:

-nLw.

CODIGO DE DATOS "A".

-Este código transfiere datos de tipo carácter de la memoria al soporte externo y a la inversa, permitiendo el manejo, entrada y salida de cualquier variable de tipo carácter, siendo su sintaxis:

-Aw.

-También tiene formato repetible siendo su sintaxis:

-nAw.

-En las entradas se debe colocar apóstrofes que delimiten la cadena si es con lista directa, pero con el código A puede evitarse y tomará la longitud de la variable asociada teniendo en cuenta:

-Si w=n todo el dato es asignado a la variable carácter.

-Si w>n los últimos n caracteres se almacenan en la variable.

-Si w<n los caracteres se ajustan a la izquierda rellenándose los

n-w caracteres de la derecha a blancos.

421

Page 422: Programacion en Java

-En las salidas existen otras consideraciones:

-Si w=n tendrá de longitud n.

-Si w>n los n caracteres se ajustan a la derecha y los w-n primeros caracteres se rellenan a blancos.

-Si w<n los w primeros caracteres de la cadena salen a la salida perdiéndose los restantes.

CODIGO DE POSICIONAMIENTO "X".

-Los códigos de posicionamiento determinan la posición dentro de una línea. La posición puede ser relativa a la actual del cursor o una posición absoluta.

-El código X salta n caracteres tanto para la salida como para la entrada en el medio externo siendo su sintaxis:

-nX.

-La opción n indica los caracteres a saltar a partir de la posición actual del cursor.

-Para la entrada salta n posiciones hacia adelante desde la posición del cursor. Para la salida en la línea o registro de salida genera n blancos y mueve el cursor n posiciones a la derecha.

-Este código no es repetible.

CODIGOS DE POSICIONAMIENTO "T", "Tl" Y "Tr".

-Estos códigos producen una tabulación en el registro y T especifica una posición absoluta dentro del registro de entrada o salida siendo su sintaxis:

-Tc.

-La opción c indica la columna dentro del registro desde donde se va a posicionar el cursor y donde comienzan las transferencias de datos.

-El tabulador Tl mueve la posición del cursor hacia la izquierda y Tr mueve la posición del cursor hacia la derecha con las siguientes sintaxis:

-Tls.

-Trs.

-La opción s indica el número de posiciones que se han de desplazar el cursor desde la posición actual.

-Si tenemos las siguientes instrucciones:

-50 FORMAT (2A, I2, T1, A, T18, A, I2)

CHARACTER *6 Nombre1, Nombre2, Apellido1*8, Apellido2*8

422

Page 423: Programacion en Java

INTEGER Edad1, Edad

READ (1, 50) Nombre1, Apellido1, Edad1, Nombre2, Apellido2, Edad y le introducimos la siguiente entrada:

-José-Martinez8Torralva25.

obtendremos la siguiente lista de variables en la salida:

-Nombre1 - José-.

-Apellido1 - Martínez.

-Edad1 - 0.

-De modo análogo, si tenemos las siguientes instrucciones:

-100 FORMAT (T8, I5, Tl2, I4, Tl6, A6)

CHARACTER *8 Cadena

INTEGER VarA, VarB

READ (*, 100) VarA, VarB, Cadena y le introducimos la siguiente entrada:

-Visita12345678.

obtendremos la siguiente lista de variables en la salida:

-M - 23456.

-N - 5678.

-Cadena - 345678.

-Estos códigos no son repetibles.

CODIGO DE POSICIONAMIENTO "/".

-Este es un código de posicionamiento vertical que da por terminado un registro o deja registros o líneas vacías y su sintaxis es:

-/.

-Puede separase por comas o no y sitúa el puntero en el primer carácter de un nuevo registro.

-En la salida causa n-1 registros o líneas vacías y no es un código de tipo repetible.

CODIGOS ESPECIALES "S", "Sp" Y "Ss".

423

Page 424: Programacion en Java

-Estos códigos sólo son válidos para formatos de salida, para números y controlan la salida del signo + en los números positivos de forma:

-S no imprime el signo +.

-Sp imprime el signo +.

-Ss no imprime el signo +.

-El formato Sp sólo valdrá hasta que sea el final de los especificadores de formato o hasta un código S o Ss.

-Estos códigos no son repetibles.

CODIGOS ESPECIALES "Bn" Y "Bz".

-Estos códigos dirigen la interpretación de los caracteres en blanco en los formatos numéricos de forma que:

-Bn ignora los blancos.

-Bz toma los blancos como ceros.

-Si un campo de entrada está en blanco se considera como cero.

CODIGO ESPECIAL "HOLLERITH".

-Este código sólo puede formar parte de un formato de salida y no es válido para entradas, siendo su sintaxis:

-wHc.

-Este código indica que habrá una salida de w caracteres que son los que van a aparecer en c. Los apóstrofes se consideran como cualquier otro carácter.

-El número de caracteres de la constante carácter determina el ancho del campo de salida.

CODIGO ESPECIAL ":".

-Este código provoca la terminación de la operación de salida si no hay más elementos en la lista de la sentencia de salida.

-Si el código es encontrado durante una entrada de datos o si quedan elementos en la lista de salida es ignorado.

CODIGO ESPECIAL " ".

-Este código facilita formatear la salida de datos en la pantalla. Este código se aplica en formatos de sentencias de salida.

424

Page 425: Programacion en Java

-Causa que el procesador suprima en la salida de acceso secuencial la separación de registros actual y el siguiente registro.

CODIGOS ESPECIALES DE CONTROL DE CARRO.

-Exigen que se escriban los códigos como primer carácter dentro de la

sentencia WRITE que emplea la impresora como salida siendo sus códigos los siguientes:

-Blanco avanza una línea.

-0 avanza dos líneas.

-1 sitúa en la primera línea de la siguiente página.

-+ imprime sobre la misma línea y no avanza.

-Si el primer carácter no es un signo de estos se toma como blanco. En la mayoría de las sentencias FORMAT tiene especificado en la columna 1 o 1X.

REGISTROS MULTIPLES.

-Si el número de elementos de la lista de entrada y salida es menor que el número de códigos de datos, los códigos sobrantes se ignoran.

-Si el número de elementos es mayor que el número de códigos de formato se van asociando los códigos de datos con los datos de izquierda a derecha.

-Cuando se alcanza el paréntesis de cierre de FORMAT se empieza un nuevo registro repitiéndose los códigos a partir del paréntesis de apertura precedente.

TEMA 5: SUBPROGRAMAS Y FUNCIONES EN FORTRAN.

UNIDADES DE PROGRAMA.

-Ante la necesidad de una organización jerárquica Fortran permite dividir el programa en módulos llamados unidades de programa. Hay dos clases de unidades de programa:

-Programa principal.

-Subprograma.

-Cada programa tiene un sólo programa principal que puede contener cero o más Subprogramas que pueden ser:

-Funciones.

-Subrutinas.

-Las Funciones pueden ser de varios tipos:

425

Page 426: Programacion en Java

-Externas, es un módulo independiente.

-Intrínsecas, es un módulo independiente.

-Unilíneas, son locales al módulo o unidad de programa donde están definidas.

-Las Subrutinas son Subprogramas que pueden ser usados para devolver un conjunto de cero a n datos y suelen ser de propósito general.

-Los módulos pueden estar uno a continuación del otro y no estarán separados a nivel lógico aunque sí lo estarán a nivel físico.

-La Función se usa cuando se necesita devolver un sólo valor, mientras que las Subrutinas se utilizan para devolver más valores o en su defecto ninguno.

FUNCIONES SENTENCIA (UNILINEA).

-Es un procedimiento especificado en una sentencia simple, con forma similar a una sentencia de asignación aritmética, lógica o carácter.

Este tipo de Funciones representan una fórmula.

-Se escribe en la misma unidad de programa que va a ser usada, son locales a la unidad de programa en la que está definida.

-Es una sentencia de tipo no ejecutable y ha de ser escrita antes de ser invocada, su sintaxis suele ser:

- Tipo Identificador (Lista de parámetros actuales)=Expresión.

-Si no se colocan parámetros, habrá que poner paréntesis igualmente.

-El tipo de dato del identificador de la Función sentencia y los argumentos ficticios están determinados por el tipo implícito del identificador.

-La invocación se realiza escribiendo su identificador y entre paréntesis los argumentos verdaderos o actuales que sustituyen a los argumentos que son ficticios.

-Los argumentos actuales pueden ser expresiones y han de corresponderse en número, orden y tipo con los argumentos ficticios. La llamada a la Función debe formar parte de una sentencia ejecutable.

-Se puede invocar el paso de varios parámetros separados por una coma o no ser invocados.

FUNCIONES INTRINSECAS.

-Son las Funciones propias del lenguaje Fortran, que ya fueron descritas en el tema 2.

-Estas Funciones se usan con el identificador de la Función seguido de los valores de los parámetros.

426

Page 427: Programacion en Java

-Habrá que tener en cuenta que los argumentos sean del mismo tipo y que correspondan dentro del rango.

FUNCIONES EXTERNAS.

-Este tipo de Funciones devuelve un valor a través del identificador de la Función aunque puede devolver otros valores. Su sintaxis es:

- Tipo FUNCTION Identificador (Lista de parámetros formales)

acción1

acción2

.......

acciónn

Identificador=Expresión

.......

RETURN

END

-El tipo es opcional y especifica el tipo de datos del valor que se devuelve. Si no se especifica se siguen las normas del tipo implícito de los identificadores.

-Esta sentencia debe ser la sentencia inicial de una Subrutina FUNCTION.

El tipo de la Función está determinado por la especificación tipo en la cabecera.

-Puede tener cualquier sentencia excepto las definiciones de otras Subrutinas. El valor asignado debe ser del mismo tipo que el de la Función.

-El fín lógico de una Función es RETURN que puede aparecer más veces.

La última sentencia de código fuente de definición de una Función es END que será el fín físico.

-Desde una Función se puede invocar otra Subrutina pero no a la misma Función y no se permite la recursividad.

LLAMADA A FUNCIONES EXTERNAS.

-La llamada a una Función ha de formar parte de una sentencia siendo la sintaxis de la llamada la siguiente:

-Identificador (Lista de parámetros actuales).

427

Page 428: Programacion en Java

-La referencia a la Función se puede realizar desde cualquier otra unidad de programa. La lista de parámetros deberá estar separada por comas.

-Los parámetros se pueden pasar por valor colocando la palabra reservada VALUE delante de las variables. No se debe modificar los valores de las variables de la lista. Es mejor duplicarlos o protegerlos.

-Los parámetros actuales deben coincidir con los argumentos ficticios con las siguientes especificaciones:

-Constantes, variables o expresiones, excepto la concatenación de operandos.

-Nombre de array.

-Funciones intrínsecas o externas.

-Subrutinas.

PASO DE ARGUMENTOS A LAS SUBRUTINAS.

-Las llamadas por valor realizan unas copias del argumento verdadero con lo que éste no cambia durante la ejecución de la Subrutina.

-Las llamadas por referencia no realizan esa copia y el argumento verdadero puede cambiar durante la ejecución de la Subrutina.

-Para saber cuando se deben pasar valores por valor o referencia nos fijaremos en la siguiente tabla:

-Por referencia si es una variable, array, elemento de array o caracteres.

-Por valor si es una constante o una expresión.

SUBRUTINAS.

-Las Subrutinas empiezan con la sentencia SUBROUTINE y puede tener cualquier sentencia excepto las usadas para definir otros módulos o unidades de programa.

-Puede devolver cero, uno o más valores siendo la transmisión por parámetros de cabecera. La ejecución termina con un fín lógico o RETURN siendo la última sentencia el fín físico o END.

-Si sólo hay un RETURN se puede omitir porque END funcionará como si fuera RETURN. La sintaxis de las Subrutinas es:

-SUBROUTINE Identificador (Lista de parámetros formales)

acción1

acción2

.......

428

Page 429: Programacion en Java

acciónn

RETURN

.......

END

-El identificador del procedimiento ha de ser único, el primer carácter debe ser una letra.

-En la Subrutina no se asocia un valor al identificador de la misma, devuelve los datos de salida modificando sus argumentos si son:

-Nombres de variable.

-Nombres de array.

-Subrutinas ficticias.

-Asteriscos.

-El tipo de los argumentos se especifica explícita o implícitamente. El argumento array se define con un tamaño fijo, anidado o ajustable.

-El argumento asterisco se usa para la salida múltiple de una Subrutina no permitiendo la autollamada o la recursión.

LLAMADA A SUBRUTINAS.

-Una Subrutina puede ser invocada desde otra Subrutina o unidad de programa principal con la siguiente sintaxis:

-CALL Identificador (Lista de parámetros actuales).

-Los argumentos deben coincidir en orden y tipo con los argumentos ficticios de la Subrutina referenciada. La ejecución de CALL causa que el control de la ejecución pase a la Subrutina referenciada.

-Los argumentos de CALL pueden ser:

-Expresiones.

-Arrays.

-Subrutinas.

-El argumento correspondiente al asterisco ha de ser del tipo *N siendo N una etiqueta de una sentencia ejecutable. Los valores se transmiten por referencia.

-Un ejemplo de la utilización de Subrutinas podría ser:

429

Page 430: Programacion en Java

-CHARACTER *20 Cab

REAL Gwr

PARAMETER (Cab='Especificación de la gravedad')

Gwr=9.82337

WRITE (*, *) Cab, Gwr

CALL Lista (Cab, Gwr)

WRITE (*, *) Cab, Gwr

SUBROUTINE Lista (Título, Datos)

CHARACTER *20 Título

REAL Datos

Título='Densidad Kg/cm'

Datos=0.57975

WRITE (*, *) Título, Datos

RETURN

END

-El resultado que proporcionará el anterior trozo de código será un resultado imprevisible, puesto que no se puede modificar una constante a una variable.

-La relación general para el paso de parámetros será la siguiente:

Parámetros actuales Parámetros formales

Variables Nombre variable

Elementos de array Nombre variable

Estructuras Nombre variable

Expresiones Nombre variable

Arrays Array

*Etiqueta Etiqueta

Subrutinas Nombre único

430

Page 431: Programacion en Java

Funciones Nombre único

SENTENCIAS "EXTERNAL" E "INTRINSIC".

-Estas sentencias se usan para cuando el argumento es el identificador simbólico de un Subprograma o Función.

-La sentencia EXTERNAL declara que un identificador es un nombre de una Función externa o de una Subrutina. No es una sentencia ejecutable y debe aparecer antes del código de sentencias ejecutables.

-La sintaxis de EXTERNAL es:

-EXTERNAL Identificador1, Identificador2, ..., Identificadorn.

-La sentencia INTRINSIC declara que un identificador es nombre de una Función intrínseca, apareciendo el identificador en la sintaxis:

-INTRINSIC Función1, Función2, ..., Funciónn.

-Las Funciones intrínsecas que no pueden ser argumentos actuales de las Subrutinas son los siguientes:

-De conversión de tipos, INT, IFIX, IDINT, FLOAT y CHAR.

-Lexicográficas, LGE, LGT, LLE y LLT.

-De máximos y mínimos.

ENTRADA MULTIPLE A UNA SUBRUTINA. SENTENCIA "ENTRY".

-Sirve para que el comienzo de una Subrutina sea en una sentencia específica contenida en la Subrutina. Puede estar en cualquier punto del programa excepto dentro de un bloque IF o DO.

-Esta sentencia es de tipo no ejecutable siendo su sintaxis:

-ENTRY Identificador (Lista de parámetros actuales).

-Los identificadores simbólicos de varios puntos de entrada tienen que ser diferentes entre sí y del nombre del procedimiento.

-Un punto ENTRY se referencia desde otro módulo. El proceso prosigue hasta encontrar un RETURN.

SALIDA MULTIPLE DE UNA SUBRUTINA. SENTENCIA "RETURN N".

-Esta sentencia causa que el control de la ejecución retorne a la unidad de programa desde donde se invocó. La sintaxis es la siguiente:

-RETURN n.

431

Page 432: Programacion en Java

-La especificación n se usará en una Subrutina, para devolver el control a una sentencia específica, sin ser la siguiente a la llamada.

-El valor de n ha de estar comprendido entre uno y el número de asteriscos de la cabecera de la Subrutina.

TEMA 6: VECTORES Y MATRICES (ARRAYS).

ARRAYS UNIDIMENSIONALES.

-Consiste en una lista de un número finito de datos del mismo tipo que se referencian por un identificador común y un número de orden que son consecutivos.

-Las variables que representan los arrays se denominan variables de subíndice. El tamaño de un vector es el número de elementos que componen el vector. Una variable de subíndice tiene el formato:

-Variable (Subíndice).

-La variable puede ser un array de los siguientes tipos:

-Numérico.

-Cadena.

-Lógico.

-Complejo.

-El subíndice puede ser:

-Constante numérica.

-Variable.

-Expresión matemática.

-Para saber una determinada posición de un elemento se deben cumplir las siguientes características:

-Todos los elementos del array son del mismo tipo.

-El vector tiene un nombre único y los elementos están ordenados por el subíndice.

DECLARACION DE UN ARRAY. SENTENCIA "DIMENSION".

-Hay dos formas de definir un vector o matriz, utilizando la forma común a todas las versiones cuyo formato será:

-DIMENSION Identificador1 (Mínimo:Máximo), ..., Identificadorn

432

Page 433: Programacion en Java

(Mínimo:Máximo).

-El número de elementos que obtendremos vendrá dado por la fórmula Máximo-Mínimo+1, sabiendo que máximo deberá ser mayor o igual que el valor de mínimo.

-El identificador es una variable con las mismas reglas. Opcionalmente se puede colocar después del Identificador un alias o ALLOCATE que se usará cuando estemos en tiempo de ejecución. Como ejemplos:

-ALLOCATE Array (Valor).

-DIMENSION Array (11:25).

-DIMENSION (14).

-Array (:).

-Lo que indica la opción ALLOCATE es que el valor de sus subíndices se va a dimensionar durante la fase de ejecución.

-El subíndice izquierdo puede tomar los valores cero, negativo o positivo al igual que el subíndice derecho, pero éste último debe ser igual o mayor que el subíndice izquierdo.

-No se puede modificar la dimensión una vez definida, y es necesario colocar corchetes para definir un array en tiempo de ejecución.

-En el ejemplo siguiente se declara un array de una dimensión que se dimensionará posteriormente:

-DIMENSION Array ALLOCATE (:).

-Para desasignar el array y liberar la memoria se utilizará la opción DEALLOCATE como sigue:

-DEALLOCATE Array (Valor).

-Las dimensiones se separan por comas y se pueden definir como máximo siete dimensiones, aunque pueden ser más dependiendo de la memoria del ordenador.

-Si el límite es uno se puede omitir el valor de mínimo, quedando la sintaxis siguiente:

-DIMENSION Identificador1 (Máximo), ..., Identificadorn (Máximo).

-La segunda forma de definir vectores es con la especificación de tipos de sintaxis:

-Tipo Identificador (Mínimo:Máximo).

-Tipo es cualquiera de los tipos definidos en Fortran. Mediante esta forma se asocia el tipo de dato al identificador y definirlo como un array con tantos elementos como haya.

-Los límites inferiores y superiores son expresiones enteras siempre con constantes nunca variables.

433

Page 434: Programacion en Java

-Otros atributos que podemos colocar después del identificador son:

-REFERENCE.

-C.

-PASCAL.

-VALUE.

-Se usarán estas opciones cuando se va a pasar el array por parámetro a otros lenguajes, por lo que habrá que normalizar el lenguaje.

-Los arrays se almacenan en la memoria de una forma distinta que en otros lenguajes, ya que se almacenan columnas a columnas unas a continuación de otras.

-Se almacenan en orden creciente de sus subíndices a partir de una posición determinada. Para sacar los elementos por filas se usará el DO implícito.

OPERACIONES CON ARRAYS Y ELEMENTOS DE UN ARRAY.

-Hay dos tipos de operaciones básicas, con los elementos que serán las mismas que permiten los tipos de datos o con estructuras como el recorrido de un array.

-Para hacer el recorrido de un array se usará una estructura del tipo FOR como:

-DO Etiqueta VarA=Izquierdo, Derecho

acción1

acción2

.......

acciónn

Etiqueta CONTINUE

ENDDO

-Se puede leer o escribir un array completo con sólo poner el Identificador del array en la sentencia de E/S.

-Se puede inicializar un array completo con sólo poner su identificador y los valores que se inicializan en la sentencia DATA.

-Consiste en obtener datos de un dispositivo externo y almacenarlos en un vector o escribir en un dispositivo los datos de un array.

-La E/S puede realizarse con el identificador del array y por tanto

434

Page 435: Programacion en Java

se procesa en total o elemento a elemento con un DO implícito o explícito.

ENTRADA Y SALIDA DE ARRAYS CON DO "IMPLICITO".

-Se utiliza en vez de la estructura de tipo FOR. Esta estructura está ya definida y hace sencilla la manipulación de los arrays en la lectura y escritura.

-Con este tipo de DO se ejecuta una sóla vez la sentencia READ de modo que se lee un sólo registro físico. Su sintaxis es:

-READ (*, *) (Identificador (VarA), VarA=Inicio, Final, Incremento).

-Cuando el incremento es uno se puede omitir. También puede emplearse para acceder a los elementos de un array de más de una dimensión para leer o escribir e inicializar.

-El DO implícito se puede anidar con tantos niveles como sea necesario o tantos niveles como dimensiones tenga el array.

-Un ejemplo del uso del DO implícito sería:

-READ (*, *) (Array (VarA), VarA=1, 20).

-El ejemplo anterior lee veinte variables estableciéndose un bucle. Otro ejemplo:

-READ (*, *) (Array (VarA), VarA=Izdo, Dcho, Paso).

-En el ejemplo anterior Paso indica que la variable toma el valor de izdo y sus sucesivos valores repitiendo el proceso hasta que dcho tiene mayor valor que izdo.

-Pero si paso es negativo entonces dcho ha de ser menor forzosamente. El siguiente ejemplo tendría como representación con DO la siguiente:

-READ (*, 100) (Array (VarA), VarB=1)

READ (*, *) ((Array (VarA, VarB), VarB=1, VarC), VarA=1, VarD)

-DO VarA=1, VarD

DO VarB=1, VarC

READ (*, *) (Array (VarA, VarB))

ENDDO

ENDDO

PASO DE ARRAYS POR PARAMETRO.

-El paso de un array se realiza por referencia y cuando un array es pasado, en realidad se pasa la dirección en memoria del primer elemento ahorrándose memoria y espacio.

435

Page 436: Programacion en Java

-Un ejemplo de ello es el siguiente:

-REAL Array (100, 200), VarA, VarB

CALL Lista (Array, VarA, VarB)

SUBROUTINE Lista (Arrayauxiliar, VarC, VarD)

-En la definición de los argumentos ficticios para los arrays en las Subrutinas o Funciones, no es necesario que sean iguales los límites superior e inferior de cada dimensión con los límites del argumento actual del array transmitido.

-Siempre se exige que una variable sea dimensionada por lo que dentro de la Subrutina se colocará la siguiente declaración:

-REAL Arrayauxiliar (100, 200).

-En esta Subrutina no se crea la variable arrayauxiliar sino que se define dicha variable. El array en esa declaración se puede ajustar siempre que la dimensión sea menor que la declarada anteriormente.

-En todo caso el tamaño del argumento ficticio para el array no puede ser mayor que el del argumento actual.

-Se puede poner un asterisco que es la opción por defecto y que indica que toma el valor de la dimensión iniciada en el programa principal.

ARRAYS DE TAMAÑO AJUSTABLE Y TAMAÑO ASUMIDO.

-El argumento ficticio array en la Función Máximo su tamaño se ajusta a N elementos que es un dato transmitido, y por tanto la definición de arrays ajustables.

-La definición de un argumento ficticio array de tamaño ajustable en una Función o en una Subrutina es la única situación en la que una definición de array puede incluir una variable en la especificación del rango de cada dimensión.

-Para definir un argumento ficticio array como asumido se especifica el límite superior de la última dimensión del array con un asterisco. El número de elementos del array ficticio es el mismo que el array pasado por parámetro.

-Sólo se puede especificar el límite superior de la última dimensión con un asterisco, aunque para los caracteres también es válido.

-Para las Funciones carácter externas usaremos la siguiente sintaxis:

-CHARACTER * (*) FUNCTION Identificador (Lista de parámetros).

TEMA 7: SENTENCIAS ESPECIALES DE FORTRAN.

SENTENCIA "EQUIVALENCE".

436

Page 437: Programacion en Java

-Esta sentencia declara que dos o más identificadores son equivalentes y al menos dos. Hace que compartan la misma posición de almacenamiento que puede ser referenciado de más de una forma.

-La sintaxis de esta orden es la siguiente:

-EQUIVALENCE (Lista de Identificadores).

-En una misma sentencia EQUIVALENCE pueden haber más de una lista de identificadores, cada una de las cuales se refiere a la misma posición de almacenamiento:

-EQUIVALENCE (Lista de Identificadores),

+ (Lista de Identificadores).

-Las variables de distinto tipo pueden hacerse equivalentes, de forma que habrá almacenamiento compartido pero no una posible conversión de tipos.

-La sentencia de almacenamiento de los identificadores de la lista comienza con la primera unidad de almacenamiento de las entidades de la lista.

-Se pueden hacer equivalentes los arrays y los elementos de los arrays.

Los índices de las variables array escritos en una lista de la sentencia deben ser constantes o expresiones formadas por constantes.

-En una sentencia EQUIVALENCE no puede provocarse que una misma variable ocupe dos posiciones de memoria distintas.

-Esta sentencia puede utilizarse entre variables o arrays de tipo carácter aunque sus longitudes no sean las mismas.

-Las variables carácter equivalentes tendrán el mismo primer carácter ubicado en la misma posición de memoria.

-En Fortran 77 no está permitido hacer equivalentes variables de tipo carácter con variables numéricas, y los nombres de Función no pueden hacerse equivalentes con otra entidad.

-Esta sentencia se suele utilizar cuando la memoria de que se dispone es muy pequeña, pero produce que la lógica del programa pueda acceder a la variable correcta.

SENTENCIA "COMMON".

-Una forma de comunicarse entre el programa o Unidad de programa que llama a una Subrutina o Función, y la Subrutina o Función llamada, es a través de la lista de argumentos.

-Con estos argumentos se referencian zonas de memoria comunes desde distintas Unidades de programa, la Unidad de programa que llama y la Unidad llamada.

-Con esta sentencia se pueden definir zonas comunes de memoria entre diversas Unidades de que forman parte un programa, entre la Unidad principal y una o varias Subrutinas o Funciones.

437

Page 438: Programacion en Java

-Es una sentencia no ejecutable que debe aparecer en la Unidad de programa que llama y en el Subprograma llamado antes de todas las sentencias ejecutables.

-En esta sentencia se listan los nombres de las variables y los nombres de los arrays con su dimensión y es una forma alternativa de comunicar datos a la lista de argumentos de los Subprogramas.

-Si un array va ha estar en una zona común se puede definir su lista de identificadores en la lista COMMON o definir el array y ponerlo en la lista de la sentencia COMMON.

-Las variables y los arrays son asignados a un almacenamiento común en el orden en que aparecen en la sentencia COMMON, siendo la sintaxis de esta:

-COMMON Identificador1, Identificador2, ..., Identificadorn.

-La sintaxis anterior pertenece a una sentencia COMMON sin nombre o en blanco, pero existe una sentencia COMMON etiquetada o con nombre.

-El orden de los identificadores especificados en una sentencia COMMON determina la equivalencia de identificadores simbólicos entre varias Unidades de un programa (El bloque común de memoria es lo global).

-La lista de identificadores deben ser de igual tipo en todas las sentencias COMMON estableciéndose las equivalencias por el orden en que están en la lista.

-Un identificador de una lista COMMON no puede figurar como argumento en una Subrutina o Función ni como parámetro en las llamadas (Puede haber un solapamiento de memoria).

-El bloque común de memoria es único para todo el programa. La memoria común se establece de forma contigua.

-En un bloque común de memoria no puede haber variables carácter y no carácter mezcladas, siendo todo de variables carácter o variables no carácter.

-Las variables o arrays que figuran en la lista de COMMON sin nombre no pueden ser inicializadas con DATA, sólo pueden inicializarse con sentencias de asignación.

SENTENCIA "COMMON" CON NOMBRE. USO CONJUNTO DE "COMMON" Y "EQUIVALENCE".

-Esta sentencia etiquetada permite definir varias zonas comunes de memoria, cada una con su nombre o etiqueta.

-Se forman de igual manera que las anteriores pero se escribe su nombre o etiqueta con dos barras (Slash) antes de la lista de variables.

-El nombre de COMMON debe ser un identificador válido y pueden definirse dos o más zonas comunes. Es conveniente usar tantas sentencias COMMON como zonas comunes haya.

-Se pueden mezclar en una zona común variables numéricas con variables o arrays lógicos.

-En el siguiente ejemplo tendremos sólamente dos bloques COMMON uno con nombre y otro sin nombre:

438

Page 439: Programacion en Java

-COMMON /Nombre-1/ VarA, VarB, VarC

COMMON VarH, VarI, VarJ

COMMON /Nombre-1/ VarK, VarL

COMMON VarM, VarN

-Una comparación entre un trozo de programa con COMMON y otro sin él podría ser el siguiente:

-REAL VarD, VarE, VarF, VarG, VarZ

READ *, VarD, VarE, VarF, VarG

VarZ=2

VarT=Función (VarD, VarE, VarF, VarG)

STOP

END

FUNCTION Función (VarA, VarB, VarC, VarX)

VarI=VarA*VarX**2+VarB*VarX+VarC

RETURN

END

-REAL VarD, VarE, VarF, VarG, VarZ

COMMON /Coeficientes/ VarD, VarE, VarF

VarZ=2

VarT=Función (VarZ)

STOP

END

FUNCTION Función (VarX)

COMMON /Coeficientes/ VarA, VarB, VarC

VarF=VarA*VarX**2+VarB*VarX+VarC

RETURN

END

439

Page 440: Programacion en Java

-Si en una sentencia COMMON se precede a los identificadores de dos barras o slash indicará que son zonas comunes sin nombre. Es indiferente el orden de definición de las zonas comunes.

-Los arrays o variables de COMMON con etiqueta pueden ser inicializados con la sentencia DATA, pero no las variables de un COMMON sin nombre.

-Un COMMON con etiqueta tiene que tener el mismo tamaño en todas las Unidades de programa.

-Las variables o arrays pueden aparecer en ambas sentencias COMMON y EQUIVALENCE siempre que no causen conflicto en el orden en el que se almacenan.

-Dos variables que estén en una zona común no pueden ser equivalentes entre sí.

SENTENCIA "SAVE".

-Esta sentencia se utiliza para almacenar estáticamente los valores o datos de una invocación a otra.

-SAVE declara que las variables locales y los arrays sean retenidos después de ejecutar RETURN o la siguiente llamada al Subprograma.

-Las variables o arrays locales contendrán el último valor adquirido en la ejecución anterior al Subprograma. Su sintaxis es:

-SAVE (Lista de identificadores).

-La lista de identificadores, que podrán ser variables, arrays o bloques COMMON, retendrá el último valor adquirido antes de la ejecución de RETURN.

-Las excepciones en las que las variables no quedan indefinidas al salir de un Subprograma son:

-Sentencias SAVE del Subprograma.

-Bloques COMMON en blanco o sin nombre.

-Bloques COMMON etiquetados y definidos en la Unidad de programa principal y uno o más Subprogramas.

-Las variables en bloques COMMON etiquetados en Subprogramas quedan indefinidos sólo cuando hay una salida desde un Subprograma pero no se pueden retener con SAVE y el nombre del bloque COMMON.

-En una lista de SAVE no pueden aparecer parámetros de Subrutinas o Funciones, ni nombres de Función y Subrutinas ni variables o arrays de bloques COMMON.

-Puede haber más de un SAVE o escribir la lista en un sólo SAVE. Si hay más de un SAVE no podrán repetirse nombres de variables o arrays.

INICIALIZACION DE VARIABLES. SENTENCIA "DATA".

-Esta sentencia permite inicializar variables con la siguiente sintaxis:

440

Page 441: Programacion en Java

-DATA Lista1 /Constantes/, ..., Listan /Constantes/.

-La lista contendrá los nombres de las variables o arrays a inicializar separados por comas e inicializa las constantes que vendrán separadas por comas.

-Puede especificarse más de una lista y sus constantes o agrupar todas las variables en una sóla lista. La inicialización se realiza en el orden en el que aparecen, de izquierda a derecha.

-Si hay constantes consecutivas iguales se podrá poner:

-Número*Constante.

-En el formato anterior Número es el número de repeticiones que se especificará de dicha forma.

-Las reglas más importantes de las sentencias DATA son las siguientes:

-El número de constantes ha de ser igual al número de elementos de la lista, variables o arrays.

-No pueden aparecer argumentos ficticios de Subprogramas, nombres de Función y elementos de bloque COMMON en blanco.

-Sólo pueden estar los bloques COMMON etiquetados en los bloques DATA.

-El tipo de variable o array a inicializar debe corresponderse con el tipo de la constante. Lo mismo para las variables de tipo carácter (Si hay exceso se ignora y si hay defecto se completa con blancos a la derecha).

-Esta sentencia no es ejecutable y puede aparecer después de las especificaciones de datos pero es mejor colocarlas antes de las sentencias ejecutables.

-Las sentencias DATA pueden utilizar el DO implícito para inicializar el array a los datos que se quieran o sólo definirlo. Un ejemplo de ello sería:

-INTEGER VarA, Orden, Alfa, Lista (100)

REAL Coeficiente (4), Epsilon (2), Pi (5), VarX (5, 5)

CHARACTER *15 Ayuda

DATA VarA /0/, Orden /3/

DATA Coeficiente /1.0, 2*3.0, 1.0/, Epsilon (1) /0.0001/

DATA ((VarX (VarI, VarJ), VarI=1, VarJ), VarJ=1.5) /15*1.0/

DATA Lista /100*0/

DATA Ayuda /'Ayuda'/

-El siguiente formato puede ir en cualquier parte del programa y tiene como misión hacer una llamada al camino o ruta especificado:

441

Page 442: Programacion en Java

-$INCLUDE 'Path Nombre.For'.

SENTENCIA "PARAMETER".

-Esta sentencia identifica constantes mediante nombres identificadores o simbólicos para que después se pueda hacer referencia a la constante por el identificador. La sintaxis de la orden es:

-PARAMETER (Identificador=Expresión constante).

-Dentro de los paréntesis se pueden especificar tantos parámetros como se quiera separándolos por comas.

-La expresión debe coincidir en su tipo con el identificador y el valor que va ha representar el identificador al evaluarse la expresión. Debe ajustarse a las reglas establecidas para las sentencias de asignación.

-El tipo de dato del identificador puede definirse de forma implícita o explícita, describiendo la sentencia de definición antes.

-La expresión constante puede hacer referencia a otro parámetro pero ha de estar definida antes en otro PARAMETER o en la misma sentencia

PARAMETER.

-Un identificador de constante no puede cambiar después el valor que se le ha impuesto.

-El ámbito de los parámetros es la Unidad de programa en que están definidos y una vez que el parámetro es definido puede ser referenciado en los sitios en que pueden referenciarse las constantes excepto:

-No se pueden usar los parámetros en una especificación de un formato.

-Un parámetro no puede usarse como parte de otra constante.

-Esta sentencia no es ejecutable y puede aparecer después de cualquier sentencia de especificación de tipo o antes de que se haga uso del parámetro.

-Cuando un valor aparece varias veces en una Unidad de programa se debe asociarle un nombre simbólico y usar dicho nombre para después hacer la referencia a dicho nombre.

INICIALIZACION DE UN COMMON CON NOMBRE. SUBPROGRAMA "BLOCK DATA".

-Este Subprograma asigna valores iniciales a variables y a arrays de un COMMON etiquetado, puesto que los COMMON en blanco se inicializan en las sentencias DATA.

-La sentencia BLOCK DATA que puede tener un identificador termina con la sentencia END y las sentencias que se pueden especificar son todas las no ejecutables para la inicialización de la lista de COMMON etiquetados y que son:

-IMPLICIT.

-PARAMETER.

442

Page 443: Programacion en Java

-DIMENSION.

-SAVE.

-COMMON.

-EQUIVALENCE.

-DATA.

-La sintaxis de este Subprograma será:

-BLOCK DATA Identificador

sentencia1

sentencia2

..........

sentencian

END

-Si el identificador se coloca es considerado como un identificador global y no puede coincidir con el nombre de una Función o el de una Subrutina.

-El COMMON etiquetado al inicializarlo hay que especificarlo en el Subprograma BLOCK DATA de forma completa aunque haya variables no inicializables.

-En un programa ejecutable pueden haber más de un BLOCK DATA pero sólo uno puede ser sin nombre y todos los demás nombres distintos. Un COMMON sólo puede estar en un sólo BLOCK DATA.

-Un ejemplo del uso de este Subprograma sería:

-BLOCK DATA Nombre

COMPLEX VarA, VarB

LOGICAL VarC, VarD

INTEGER VarI, VarJ, VarK, Lista

REAL VarX

COMMON /Bloque1/ VarX (10), VarI, VarJ, VarA

COMMON /Bloque2/ Lista (6), VarC, VarD, VarB

DATA VarX /10*0.0/, VarI, VarJ /1, 0/, VarC /False/

443

Page 444: Programacion en Java

DATA VarA, VarB, /2*(0, 1)/

END

-Normalmente este tipo de Subprogramas se suelen colocar cuando acaba el programa principal.

TIPO DE DATO COMPLEJO.

-Este tipo de datos se representa por un par ordenado de números reales de doble precisión, enteros o una combinación de ellos encerrados entre paréntesis y separados por comas.

-Para definir un identificador de tipo complejo contamos con COMPLEX, y pueden haber variables, arrays complejos y Funciones complejas.

-La memoria que ocupa una variable compleja es el doble de una variable real. Se puede asignar una constante compleja, otra variable compleja o una expresión compleja inicializándose con DATA.

-Cuando a la variable compleja se le quiere asignar un número complejo que tiene la parte real, la parte imaginaria o ambas debe usarse la Función intrínseca COMPLEX.

-Los números complejos pueden sumarse, restarse, multiplicarse, elevarse a una potencia y dividirse. No puede usarse en la expresión aritmética de la sentencia IF aritmético, y no puede usarse como subíndice de un array.

-Otras Funciones que tienen los números complejos son:

-AIMAG (Expresión numérica) (Parte imaginaria como número real).

-CONJ (Expresión numérica) (Devuelve el complejo conjugado).

-Otras Funciones internas que tienen un nombre específico para el argumento complejo y el valor que devuelven es también complejo son:

-CSQRT (Expresión numérica) (Raíz cuadrada de un complejo).

-CABS (Expresión numérica) (Módulo del complejo).

-CEXP (Expresión numérica) (Función exponencial de un complejo).

-CLOG (Expresión numérica) (Logaritmo natural de un complejo).

-CSIN (Expresión numérica) (Seno de un complejo).

-CCOS (Expresión numérica) (Coseno de un complejo).

SENTENCIA "PAUSE".

-Esta sentencia hace una parada temporal en la ejecución de un programa para detener la salida hasta que el usuario haya podido leer toda la información. Su sintaxis es:

444

Page 445: Programacion en Java

-PAUSE.

-PAUSE 'Cadena de caracteres'.

-PAUSE Número (Constante de hasta cinco dígitos).

-Al producirse la parada se visualiza un mensaje propio regido por el número indicado o la cadena si ha sido especificada.

ASIGNACION DE ETIQUETAS A VARIABLES ENTERAS. SENTENCIA "ASSIGN TO".

-Esta sentencia permite asignar un número de etiquetas por una constante entera a una variable entera siendo su sintaxis:

-ASSIGN Etiqueta TO Variable.

-Etiqueta es la etiqueta de una sentencia ejecutable o de una sentencia FORMAT, siendo variable el identificador de una variable entera.

-Después de la ejecución de ASSIGN el valor de la variable no puede ser considerada como un dato entero.

-Si la etiqueta asignada a la variable es la de una sentencia FORMAT, la variable puede ser usada como un identificador de formato.

-Si la etiqueta asignada es ejecutable la variable puede ser usada en un GOTO asignado como:

-GOTO Variable.

-GOTO Variable (Etiqueta1, Etiqueta2, ..., Etiquetan).

-Cuando se ejecuta una sentencia GOTO asignada el control del programa es transferido a la sentencia con la etiqueta del último valor asignado a la variable con ASSIGN.

TEMA 8: TRATAMIENTO DE CADENAS EN FORTRAN.

CADENAS DE CARACTERES.

-Una cadena es un conjunto de caracteres encerrados entre apóstrofes.

Si se quiere representar un apóstrofe dentro de una cadena se deberá representar por dos apóstrofes consecutivos.

-Una cadena se declara con la siguiente sintaxis:

-CHARACTER *Número Lista de variables.

-Aquí número representa el número de caracteres de las variables de una cadena. Un array que contiene caracteres se define con una sentencia CHARACTER y declarada de dos modos distintos.

LAS CADENAS COMO ARGUMENTO DE SUBPROGRAMAS.

445

Page 446: Programacion en Java

-Un Subprograma puede especificar una cadena de caracteres sin darle una longitud específica y equivale a la longitud de una array con una variable entera.

-Se puede definir en un Subprograma un array de n variables cada una con su cadena de caracteres sin especificar la longitud de cadena en la sentencia CHARACTER.

-La longitud de la cadena es siempre positiva nunca igual a cero, y dicha longitud no se puede alterar aunque sí asignar cadenas cuya longitud es diferente.

-Una cadena con longitud más corta que la de la variable, rellena a blancos por la derecha y si es más larga la trunca.

ASIGNACION DE VALORES A LAS CADENAS.

-Se realiza con la sentencia de asignación y una constante de caracteres usándose una variable cadena para inicializar otra variable de cadena.

-Si los caracteres asignados no coinciden con la longitud se rellenan a blancos y si es mayor que la longitud se trunca por la derecha.

COMPARACION DE CADENAS.

-Esta comparación se realiza carácter a carácter de izquierda a derecha con las siguientes reglas:

-Si las cadenas tienen igual longitud y los caracteres son los mismos, las cadenas son iguales.

-Si una cadena es más corta que la otra se añaden blancos a la derecha de la otra cadena, de modo que pueda proceder a la evaluación como si las cadenas fueran iguales.

-Las reglas de ordenación típicas son las siguientes:

-Las letras mayúsculas están ordenadas de A a Z.

-Los dígitos ordenados de 0 a 9.

-El carácter blanco es menor que cualquier letra o número.

SUBCADENAS.

-Es cualquier cadena que representa un subconjunto de la cadena original y mantiene el orden original. Para especificar una subcadena de una variable de carácter o un elemento de un array de carácter se usa:

-Nombrecadena ( Expresión1 : Expresión2 ).

-Expresión1 es la posición en nombrecadena del primer carácter de la subcadena y expresión2 es la posición en nombrecadena del último carácter de la subcadena.

-Expresión1 y expresión2 deben ser del tipo entero y cumplir:

-1<=Expresión1<=Expresión2<=Longitud de la cadena.

446

Page 447: Programacion en Java

-Si se omite la expresión1 se toma por defecto uno. Si se omite la expresión2 se toma el valor de la longitud de la cadena original, siendo la subcadena:

-Expresión2-Expresión1+1.

CONCATENACION DE CADENAS.

-Consiste en combinar dos o más cadenas de caracteres en una única cadena, siendo el operador que realiza la concatencación o unión de cadenas el siguiente:

-//.

FUNCION LONGITUD. SENTENCIAS "LEN" Y "GETLEN".

-LEN determina la longitud de la cadena de caracteres argumento siendo su sintaxis:

-LEN (Cadena de caracteres).

-Si la cadena de caracteres es una constante de carácter su longitud es el número de caracteres. Si es una variable de cadena o elemento de array la longitud es la definida en la declaración.

-Si cadena es una subcadena con el formato (Expresión1:Expresión2) su longitud es la siguiente:

-Expresión2-Expresión1+1.

-GETLEN calcula la longitud de una cadena de caracteres excluyendo a los caracteres en blanco siendo su formato:

-GETLEN (Cadena de caracteres).

FUNCIONES DE TRATAMIENTO DE CARACTERES. SENTENCIAS "CHAR" E "ICHAR".

-CHAR determina el carácter de la cadena que ocupa la posición relativa en la secuencia de caracteres ASCII siendo su sintaxis:

-CHAR (Posición).

-El valor de posición debe estar entre 0 y 255 caracteres de la cadena.

-ICHAR es la Función inversa de CHAR. El argumento es un carácter y la Función devuelve un entero que es la posición del carácter en la secuencia ordenada de caracteres ASCII con el formato:

-ICHAR (Carácter).

FUNCION DE BUSQUEDA. SENTENCIA "INDEX".

-Esta Función localiza una subcadena dentro de otra. Devuelve un valor entero que indica la posición inicial de la cadena de caracteres destino dentro de la cadena original siendo su sintaxis:

-INDEX (Cadena fuente, Cadena destino).

447

Page 448: Programacion en Java

-Si la cadena destino no existe el formato devuelve el valor cero.

OTRAS FUNCIONES.

-La Función LEN_TRIM devuelve la longitud de la cadena dada sin los espacios en blanco siendo su sintaxis:

-LEN_TRIM (Cadena de caracteres).

-La Función SCAN busca una subcadena en una cadena dada y muestra la primera posición en la que coinciden ambas cadenas, buscando carácter a carácter, siendo su sintaxis:

-SCAN (Cadena1, Cadena2).

-La Función VERIFY devuelve un entero y verifica que una cadena está incluída en otra, devolviendo la posición del carácter que sea distinto de los demás y siendo su sintaxis:

-VERIFY (Cadena1, Cadena2).

-Otras Funciones que devuelven un valor lógico y que sirven para la comparación son:

-LGE (Cadena1, Cadena2), verifica si cadena1 es mayor o igual que cadena2.

-LGT (Cadena1, Cadena2), verifica si cadena1 es mayor que cadena2.

-LLE (Cadena1, Cadena2), verifica si cadena1 es menor o igual que cadena2.

-LLT (Cadena1, Cadena2), verifica si cadena1 es menor que cadena2.

-En estas cuatro últimas Funciones el argumento debe ser siempre un carácter.

TEMA 9: FICHEROS.

INTRODUCCION.

ESTRUCTURA DE UN FICHERO.

-Un fichero es una colección de datos organizados de alguna manera y almacenados generalmente en disco o cinta.

-Un fichero está formado por registros y estos constan de campos que pueden ser numéricos o de caracteres.

-Los ficheros formateados pueden ser editados, imprimirse o visualizarse mientras que los ficheros no formateados no pueden hacer esas acciones.

Aunque la lectura y la escritura son más rápidas y ocupan poca memoria.

ORGANIZACION DE FICHEROS.

-Se consideran dos tipos de acceso a los registros de un fichero que son los siguientes:

448

Page 449: Programacion en Java

-Acceso Secuencial.

-Acceso Directo.

-El acceso Secuencial implica el acceso a un fichero según el orden de almacenamiento de sus registros, uno a uno.

-El acceso Directo implica situarse en un registro determinado sin que ello implique la consulta de los registros precedentes.

-La Organización de un fichero define la forma en que los registros se disponen sobre el soporte de almacenamiento:

-Organización Secuencial.

-Organización Directa.

-Organización Indexada.

-Un fichero con organización Secuencial es una sucesión de registros almacenados consecutivamente sobre un soporte externo de tal modo que para acceder al registro n, necesariamente hay que pasar por los n-1 registros precedentes.

-Un fichero con organización Directa exige soporte direccionable. Cada posición se localiza por su dirección absoluta, que en el caso del disco suele venir definida por número de pista y de sector.

-El lenguaje Fortran no es muy fuerte es la manipulación de archivos aunque tiene capacidad para manipularlos.

-Los archivos suelen ser pequeños y la información a tratar suele estar en sentencias DATA.

APERTURA DE UN FICHERO. SENTENCIA "OPEN".

-Esta sentencia es la encargada de la apertura de un fichero, conectando un fichero a un número de Unidad, de forma que para referirse después al fichero se hará con el número de Unidad establecido en la apertura.

-Cuando se quiere crear un fichero en un programa se utilizará la sentencia OPEN para que quede en una Unidad.

-La sentencia OPEN también es usada para declarar las propiedades del fichero, si es Secuencial o Directo, si es Formateado o no Formateado y otras propiedades.

-La sintaxis de la sentencia será la siguiente:

-OPEN ( UNIT= N, FILE='Nombre' , ACCESS=Tipo , FORM=Formato ,

STATUS=Estado , IOSTAT=N , ERR=Etiqueta , BLANK=Tipo ,

RECL=N , BLOCKSIZE=N , MODE=Tipo ).

-La sentencia OPEN es la primera sentencia que debe aparecer al utilizar un fichero.

449

Page 450: Programacion en Java

-La opción UNIT siempre debe figurar en la sentencia OPEN y se podrá omitir, en cuyo caso N deberá figurar como primer parámetro.

-Esta opción se encarga de asignar un canal de comunicación para ejecutar el archivo.

-La opción FILE indica el nombre del fichero que va a estar conectado a la unidad N. Si no se coloca el nombre del fichero se podrá pasar por parámetro el nombre de dicho fichero.

-La opción ACCESS, donde Tipo es una expresión de tipo carácter e indica el tipo de fichero que se va a utilizar, siendo por defecto Secuencial y puediendo tomar los valores:

-SEQUENTIAL.

-DIRECT.

-La opción FORM, donde Formato es una expresión de tipo carácter en que el valor por defecto es Secuencial, asigna un formato al fichero, siendo los valores que puede tomar:

-FORMATTED.

-UNFORMATTED.

-BINARY.

-Si la opción es omitida OPEN asumirá FORMATTED si el fichero es de tipo Secuencial y UNFORMATTED si es un fichero Directo. BINARY se utiliza en Fortran 90 y representa a los ficheros binarios.

-La opción STATUS, donde Estado es una expresión de tipo carácter y se usa para saber si el fichero ya existe o es nuevo y por tanto va a ser creado. Sus opciones son las siguientes:

-OLD (Si el fichero existe, sino produce error).

-NEW (Crea el nuevo fichero y FILE no debe existir en la Unidad).

-SCRATCH (Si crea un fichero temporal y no debe darse nombre al fichero).

-UNKNOWN (Si no se sabe si existe o no el fichero siendo la opción por defecto).

-Un fichero SCRATCH desaparece cuando se cierra la Unidad o cuando termina la ejecución.

-La opción IOSTAT, donde N es un identificador de una variable entera.

Almacena un código numérico de cómo se ejecuta la sentencia OPEN. Si OPEN se ejecuta sin error se almacena un cero en N.

-La opción ERR, donde Etiqueta es la sentencia donde se bifurca incondicionalmente si el fichero no puede ser abierto al producirse un error.

-La opción BLANK, donde Tipo es una expresión de tipo carácter que puede tomar los siguientes valores:

450

Page 451: Programacion en Java

-NULL (Los blancos son ignorados y es opción por defecto).

-ZERO (Los blancos son interpretados como ceros).

-Esta opción se usa para especificar la interpretación de los espacios en blanco en los datos de entrada.

-La opción RECL, donde N es una expresión entera, indica la longitud en caracteres de los registros en un fichero de acceso Directo. Sólo debe aparecer en los ficheros Directos.

-La opción BLOCKSIZE, donde N es un número entero, asigna un tamaño al buffer de lectura intermedio.

-La opción MODE, donde Tipo es una cadena de caracteres, es el modo de apertura del fichero. La opción por defecto es la de lectura escritura siendo las opciones:

-READ.

-WRITE.

-READWRITE.

-Todas las opciones son opcionales excepto UNIT que es obligatorio, y si el fichero es de acceso Directo es obligatorio especificar RECL.

ESCRITURA DE UN FICHERO SECUENCIAL.

-Consiste en transferir información desde la memoria principal al soporte externo donde está el fichero.

-Para escribir en un fichero Secuencial se usa WRITE especificando la Unidad a la que está conectada el fichero indicando que el fichero es Secuencial.

-La sentencia de escritura tiene la siguiente sintaxis:

-WRITE ( UNIT= N , FORM=Formato , ERR=Etiqueta , IOSTAT=N ,

ENDFILE=Unidad ).

-La única opción obligatoria sería UNIT, todas las demás han quedado explicadas.

-Al crear un fichero los registros se escriben uno a continuación del otro, escribiendo un registro especial EOF al final de los datos escritos.

-Normalmente con CLOSE el registro EOF es automáticamente escrito al cerrar el fichero, aunque se puede usar la siguiente sentencia:

-ENDFILE N.

-ENDFILE ( UNIT= N , ERR=Etiqueta , IOSTAT=N ).

451

Page 452: Programacion en Java

-Donde N es el número de la Unidad a la que se conectó el fichero Secuencial. La sintaxis más empleada es la primera. Todas las demás opciones ya han quedado explicadas.

-Si se ejecuta ENDFILE, el nombre de la Unidad a la que está conectado un fichero nuevo, sin datos, creará un fichero vacío.

SENTENCIA DE CIERRE DE UN FICHERO "CLOSE".

-Esta sentencia rompe la conexión de un fichero con la Unidad a la que se conectó un OPEN siendo su sintaxis:

-CLOSE ( UNIT= N , STATUS=Tipo , IOSTAT=N , ERR=Etiqueta ).

-La opción UNIT, siendo N un entero, es el número de la Unidad donde se encuentra el fichero que se va a desconectar y puede omitirse, en cuyo caso, será el primer parámetro de CLOSE.

-La opción STATUS, siendo Tipo una expresión tipo carácter indica el estado del fichero en ese momento, puediendo tener los valores:

-KEEP (El fichero se guarda después de ser cerrado).

-DELETE (El fichero es borrado perdiéndose después del cierre).

-Por defecto toma el valor KEEP, excepto para los ficheros que son de tipo SCRATCH.

-Los posibles valores que se pueden sacar de las opciones FORM y ACCESS son los siguientes:

-Registros Secuencial-Formateados.

-Registros Secuencial-No formateados.

-Registros Directo-Formateados.

-Registros Directo-No formateados.

-Registros Binario-Formateado.

-Registros Binario-No formateado.

-Los registros de tipo Secuencial-Formateados se almacenan en código ASCII y permiten verse con cualquier editor de texto y manipularlos.

-Si tenemos las siguientes instrucciones:

-VarI=4

OPEN (33, FILE='FSEQ')

WRITE (33, '(A, I3)') 'RECORD', I/3

WRITE (33, 'C3')

452

Page 453: Programacion en Java

WRITE (33, '(11H El Reg-N3)')

CLOSE (33)

-La salida que dará este ejemplo serán tres segmentos de longitud variable cuya estructura de registros será la siguiente:

-Se puede observar como después de la primera marca de fichero OA, el segundo registro no aparece por ser de 0 bytes, mientras que el primero tendrá 9 bytes y el tercero tendrá 11 bytes.

-Los registros de tipo Secuencial-No formateados tienen longitud variable y no están en código ASCII, sino otro código que lo introduce el programa y lo organiza en bloques físicos de 128 bytes como máximo.

-Si tenemos las siguientes instrucciones:

-CHARACTER Array (3)

INTEGER *4 Datos (35)

DATA Datos /35*-1/, Array / 'X', 'Y', 'Z'/

OPEN (33, FILE='UFSEQ', FORM='UNFORMATTED')

WRITE (33) Datos

WRITE (33) Array

CLOSE (33)

-La salida que dará este ejemplo será la siguiente:

-Como el bloque tiene un tamaño máximo de 128 bytes por registro, al sacar todos los elementos del Array Datos (Que necesitará 140 bytes) se particiona en dos registros uno de 128 bytes y otro de 12 bytes.

-Los registros de tipo Directo-Formateados tienen la misma longitud, que se deberá especificar en la longitud del registro.

-A cada registro siempre se le añade el carácter de control de carro (OD) y el carácter de avance de línea (OA). Si tenemos las siguientes instrucciones:

-OPEN (33, FILE='FDIR', FORM='FORMATTED', ACCESS='DIRECT',

RECL=10)

WRITE (33, '(A)', REC=1) 'Registro 1'

WRITE (33, '(I5)', REC=3) 30303

CLOSE (33)

453

Page 454: Programacion en Java

-La salida que dará este ejemplo será la siguiente:

-Se puede ver que del registro primero se pasa al tercero. En los ficheros Directos, se reserva espacio para los registros.

-De esa forma si luego viene un registro segundo se insertaría entre los registros primero y tercero.

-Los registros de tipo Directo-No formateados es la opción por defecto, en la que la longitud del registro es la misma para todos pero el compilador no introduce los caracteres OD y OA.

LECTURA DE UN FICHERO SECUENCIAL.

-Transfiere la información contenida en los registros del fichero a la memoria del ordenador representada por las variables que aparecen en la lista de la sentencia de lectura.

-La sentencia READ tiene la sintaxis para ficheros Secuenciales siguiente:

-READ ( UNIT= N, FORM=Formato , END=Etiqueta , ERR=Etiqueta ,

IOSTAT=N , REC=N ) Lista de variables.

-La opción que siempre debe figurar es el número de Unidad perteneciente al de apertura del fichero Secuencial.

-La opción END, donde Etiqueta es un número entero indica que al detectar el carácter de fin de fichero transfiera el control de ejecución del programa a la etiqueta indicada.

-Todas las demás opciones ya se han explicado.

-Para leer un fichero la primera opción a realizar es abrirlo en la opción STATUS con el valor OLD.

POSICIONAMIENTO EN UN FICHERO SECUENCIAL.

-La lectura y escritura de registros se realiza uno a uno y en serie.

Hay dos sentencias para regresar un registro (BACKSPACE) y para posicionarse al principio del fichero (REWIND).

-La sintaxis de BACKSPACE es la siguiente:

-BACKSPACE N.

-BACKSPACE ( UNIT= N, IOSTAT=N , ERR=Etiqueta ).

-N es el número de la Unidad a la cual está conectado el fichero. Si no hay registro anterior porque el fichero se acaba de abrir no tiene efecto.

-Todas las demás opciones ya se han explicado.

-La sentencia REWIND tiene la siguiente sintaxis:

454

Page 455: Programacion en Java

-REWIND N.

-REWIND ( UNIT= N, IOSTAT=N , ERR=Etiqueta ).

-El efecto es rebobinar el fichero al comienzo, apuntando al primer registro. Todas las demás opciones ya se han explicado.

CREACION DE UN FICHERO DE ACCESO DIRECTO.

-Cada registro de un fichero de acceso Directo es identificado únicamente por su posición lógica en el fichero o número de registro que es un entero de 1 a n.

-El acceso a los registros se hace siempre a partir del número de registro con el que fue creado. En un fichero de acceso Directo no tiene sentido el registro EOF.

-Por ello ENDFILE no se debe ejecutar sobre estos ficheros, porque no tiene efecto.

-En un fichero de acceso Directo se reserva espacio de almacenamiento para todos los posibles registros.

-En los ficheros de acceso Directo los datos de entrada o de salida deben ir siempre con formato. Este tipo de ficheros es creado al abrirlo, especificando acceso directo y dándole una longitud.

-Los ficheros no formateados guardan la información en código binario, siendo el acceso a los registros más rápidos porque se elimina el tiempo de conversión a binario.

-Los registros pueden ser escritos o leídos en cualquier orden. Siempre hay que indicar en las sentencias READ y WRITE un parámetro REC que indicará el número de registro a acceder.

-El proceso para leer un registro es similar al de escribir. Hay que especificar el número de registro que se quiere leer.

SENTENCIA "INQUIRE".

-Con esta sentencia durante la ejecución de un programa puede obtenerse información sobre las características de una Unidad o de un fichero.

-Puede ejecutarse antes de que un fichero haya sido abierto, conectado a una Unidad.

-La información que puede requerirse de un fichero o de una Unidad es muy variada. La sintaxis para esta sentencia es la siguiente:

-INQUIRE (UNIT=N, Lista de especificadores).

-Donde N es un número de unidad sobre el que se va a preguntar siendo la lista de especificadores opcionales los siguientes:

-ACCESS=Var*10 ('SEQUENTIAL' o 'DIRECT').

-BLANK=Var*4 ('NULL' o 'ZERO').

-DIRECT=Var*7 ('YES', 'NO' o 'UNKNOWN').

455

Page 456: Programacion en Java

-EXIST=Var ('.TRUE.' o '.FALSE.').

-FORM=Var ('UNFORMATTED' o 'FORMATTED).

-FORMATTED=Var ('YES', 'NO' o 'UNKNOWN').

-NAME=Var*3 ('.TRUE.', '.FALSE.' o 'Nombre').

-NEXTREC=N (Registro después del último accedido).

-NUMBER=N (Número de Unidad igual a UNIT).

-OPENED=Var ('.TRUE.' o '.FALSE.').

-RECL=N (Longitud del registro).

-SEQUENTIAL=Var ('YES', 'NO' o 'UNKNOWN').

-UNFORMATTED=Var ('YES', 'NO' o 'UNKNOWN').

-IOSTAT=N (Código de estado).

-ERR=Etiqueta (Bifurcaciones de error).

-BINARY=Var*10 ('YES', 'NO' o 'UNKNOWN').

-BLOKSIZE=N (Tamaño del buffer).

-Si se produce error en INQUIRE todas las variables que figuran en la sentencia queda un valor indefinido excepto la variable entera de STATUS y las variables de los especificadores pueden ser elementos de un Array.

-Otra posibilidad de INQUIRE es preguntar por el fichero con la siguiente sintaxis:

-INQUIRE (FILE='Nombre', Lista de especificadores).

-La lista de especificadores es la misma que la anterior. Si el nombre del fichero es un identificador válido para el sistema y si el fichero existe se añadirán los siguientes:

-DIRECT, FORMATTED, NAME, SEQUENTIAL y UNFORMATTED.

-Si el fichero está abierto puede aplicarse:

-ACCESS, BLANK, FORM, NEXTREC, NUMBER, BELL, IOSTAT y ERR.

-La ventaja es que provee de información muy valiosa para abrir ficheros o evitar errores que abortan la ejecución de un programa.

FICHEROS INTERNOS.

456

Page 457: Programacion en Java

-Cuando el programa ejecuta READ o WRITE con un fichero o dispositivo externo se realizan las dos operaciones conjuntamente.

-En los ficheros internos la transferencia de información se produce entre dos áreas de memoria interna. Un fichero interno es un área de almacenamiento interno.

-Las entradas o salidas con ficheros internos deben ser siempre formateadas con los códigos de formato deseados por el programador.

-Con los ficheros internos hay que especificar siempre los códigos de formato en las sentencias WRITE y READ.

-Para acceder a más de un registro en un fichero interno hay que ejecutar una sóla vez la sentencia READ o WRITE.

-Las opciones que no se permiten usar son las siguientes:

-OPEN, CLOSE, INQUIRE, REWIND, BACKSPACE y ENDFILE.

FICHEROS BINARIOS.

-Es un fichero de tipo Secuencial aunque también podemos tener ficheros Directos, pero de esta forma permite recibir o escribir más de un registro a la vez.

-No se separan los registros y se lee de la misma manera pero en el Directo hay que poner en la opción de formato lo siguiente:

-FORM='BINARY'.

-Si tenemos las siguientes instrucciones:

-INTEGER *1 VarA (4)

CHARACTER VarB (3)

CHARACTER *4 VarC

DATA VarA /4*7/

DATA VarC /'Esto'/, VarB /'A', 'B', 'C'/

OPEN (33, FILE='FBIN', FORM='BINARY')

WRITE (33) VarB, VarC

WRITE (33) 'Que', 'Quieres'

WRITE (33) VarA

CLOSE (33)

457

Page 458: Programacion en Java

-La salida que producirá en el registro será la siguiente:

-Notar que la separación es imaginaria. Siempre en un fichero Directo se han de inicializar las variables normalmente.

458

Page 459: Programacion en Java

Tema 0. Introducción.

Hay distintos lenguajes que nos permiten dar instrucciones a un ordenador. El más directo es el propio del ordenador, llamado "lenguaje de máquina" o "código máquina", formado por secuencias de ceros y unos.

Este lenguaje es muy poco intuitivo para nosotros, y difícil de usar. Por ello se recurre a otros lenguajes más avanzados, más cercanos al propio lenguaje humano (lenguajes de alto nivel), y es entonces el mismo ordenador el que se encarga de convertirlo a algo que pueda manejar directamente.

Se puede distinguir dos tipos de lenguajes, según se realice esta conversión:

1. En los intérpretes, cada instrucción que contiene el programa se va convirtiendo a código máquina antes de ejecutarla, lo que hace que sean más lentos.

2. En los compiladores, se convierte todo el programa en bloque a código máquina y después se ejecuta. Así, hay que esperar más que en un intérprete para comenzar a ver trabajar el programa, pero después éste funciona mucho más rápido.

La mayoría de los lenguajes actuales son compiladores, y suelen incluir:

• Un editor para escribir o revisar los programas. • El compilador propiamente dicho, que los convierte a código máquina. • Otros módulos auxiliares, como enlazadores (linkers) para unir distintos subprogramas, y

depuradores (debuggers) para ayudar a descubrir errores.

Algunos de los lenguajes más difundidos son:

• BASIC, que durante mucho tiempo se ha considerado un buen lenguaje para comenzar a aprender, por su sencillez, aunque se podía tender a crear programas poco legibles. A pesar de esta "sencillez" hay versiones muy potentes, incluso para programar en entornos gráficos como Windows (es el caso de Visual Basic).

• COBOL, que fue muy utilizado para negocios, aunque últimamente está bastante en desuso.

• FORTRAN, concebido para ingeniería, operaciones matemáticas, etc. También va quedando desplazado.

• Ensamblador, muy cercano al código máquina, pero sustituye las secuencias de ceros y unos (bits) por palabras más fáciles de recordar, como MOV, ADD, CALL o JMP.

• C, el mejor considerado actualmente, porque no es difícil y permite un grado de control del ordenador muy alto, combinando características de lenguajes de alto y bajo nivel. Además, es muy transportable: existe un estándar, el ANSI C, lo que asegura que se pueden convertir programas en C de un ordenador a otro o de un sistema operativo a otro con bastante menos esfuerzo que en otros lenguajes.

• PASCAL, el lenguaje estructurado (ya se irá viendo esto más adelante) por excelencia, y que en algunas versiones tiene una potencia comparable a la del lenguaje C, como es el caso de Turbo Pascal en programación para DOS y Windows. Frente al C tiene el inconveniente de que es menos portable, y la ventaja de que en el caso concreto de la

Curso de Pascal

459

Page 460: Programacion en Java

programación para DOS, Turbo Pascal no tiene nada que envidiar la mayoría de versiones del lenguaje C, pero resulta más fácil de aprender, es muy rápido, crea ficheros EXE más pequeños, etc.

Dos conceptos que se mencionan mucho al hablar de programación son "programación estructurada" y "programación orientada a objetos".

La programación estructurada consiste en dotar al programa de un cierto orden, dividiéndolo en bloques independientes unos de otros, que se encargan de cada una de las tareas necesarias. Esto hace un programa más fácil de leer y modificar.

La programación orientada a objetos se tratará más adelante, cuando ya se tenga una buena base de programación. De momento, anticipemos que "Object Pascal" es el nombre que se suele dar a un lenguaje Pascal que permita programación orientada a objetos (como es el caso de Turbo Pascal), y que "C++" es una ampliación del lenguaje C, que también soporta P.O.O.

En lo que sigue vamos a ver los fundamentos de la programación en Pascal, primero intentando ceñirnos al Pascal estándar, y luego ampliando con las mejoras que incluye Turbo Pascal, la versión más difundida.

Tema 1. Generalidades del Pascal.

Como lenguaje estructurado que es, muchas veces habrá que dividir en bloques las distintas partes que componen un programa. Estos bloques se denotan marcando su principio y su final con las palabras begin y end.

La gran mayoría de las palabras clave de Pascal (palabras con un significado especial dentro del lenguaje) son palabras en inglés o abreviaturas de éstas. No existe distinción entre mayúsculas y minúsculas, por lo que "BEGIN" haría el mismo efecto que "begin" o "Begin". Así, lo mejor será adoptar el convenio que a cada uno le resulte más legible: algunos autores emplean las órdenes en mayúsculas y el resto en minúsculas, otros todo en minúsculas, otros todo en minúsculas salvo las iniciales de cada palabra... Yo emplearé normalmente minúsculas, o a veces mayúsculas y minúsculas combinadas cuando esto haga más legible algún comando "más enrevesado de lo habitual" (por ejemplo, si están formados por dos o más palabras inglesas como OutText o SetFillStyle.)

Cada sentencia (u orden) de Pascal debe terminar con un punto y coma (;), salvo el último "end", que lo hará con un punto.

También hay otras tres excepciones: no es necesario un punto y coma después de un "begin", ni antes de una palabra "end" o de un "until" (se verá la función de esta palabra clave más adelante), aunque no es mala técnica terminar siempre cada sentencia con un punto y coma, al menos hasta que se tenga bastante soltura.

Cuando definamos variables, tipos, constantes, etc., veremos que tampoco va punto y coma después de las cabeceras de las declaraciones. Pero eso ya llegará...

Con poco más que lo visto hasta ahora ya se podría escribir un pequeño programa que hiciera aparecer el mensaje "Hola" en la pantalla:

program Saludo;

460

Page 461: Programacion en Java

begin write('Hola'); end.

La palabra program no es necesaria en muchos compiladores actuales, pero sí lo era inicialmente en Pascal estándar, y el formato era

program NombrePrograma (input, output);

(para indicar que el programa iba a manejar los dispositivos de entrada y salida). Por ejemplo, como este programa escribe en la pantalla, si se usa el Pascal de GNU, deberá poner:

program Saludo(output);

Aunque para nosotros no sea necesaria la línea de "program", su empleo puede resultar cómodo si se quiere poder recordar el objetivo del programa con sólo un vistazo rápido a su cabecera.

Saludo es un identificador que nos va a servir para indicar el nombre del programa. Los "identificadores" son palabras que usaremos para referirnos a una variable, una constante, el nombre de una función o de un procedimiento, etc.

Una variable equivale a la clásica incógnita "x" que todos hemos usado en matemáticas, que puede ser cualquier número. Ahora nuestras "incógnitas" podrán tener cualquier valor (no sólo un número: también podremos guardar textos, fichas sobre personas o libros, etc.) y nombres más largos (y que expliquen mejor su contenido).

Estos nombres de "identificadores" serán combinaciones de letras y números, junto con algunos (pocos) símbolos especiales, como el de subrayado (_). No podrán empezar con un número, sino por un carácter alfabético (A a Z, sin Ñ ni acentos) o un subrayado, y no podrán contener espacios.

Así, serían identificadores correctos: Nombre_De_Programa, programa2, _SegundoPrograma pero no serían admisibles 2programa, 2ºprog, tal&tal, Prueba de programa, ProgramaParaMí (unos por empezar por números, otros por tener caracteres no aceptados, y otros por las dos cosas).

Las palabras "begin" y "end" marcan el principio y el final del programa, que esta vez sólo se compone de una línea. Nótese que, como se dijo, el último "end" debe terminar con un punto.

"Write" es la orden que permite escribir un texto en pantalla. El conjunto de todo lo que se desee escribir se indica entre paréntesis.

Cuando se trata de un texto que queremos que aparezca "tal cual", éste se encierra entre comillas (una comilla simple para el principio y otra para el final, como aparece en el ejemplo).

461

Page 462: Programacion en Java

El punto y coma que sigue a la orden "write" no es necesario (va justo antes de un "end"), pero tampoco es un error; así que podemos dejarlo, por si después añadimos otra orden entre "write" y "end".

La orden "write" aparece algo más a la derecha que el resto. Esto se llama escritura indentada, y consiste en escribir a la misma altura todos los comandos que se encuentran a un mismo nivel, algo más a la derecha los que están en un nivel inferior, y así sucesivamente. Se irá viendo con más detalle a medida que se avanza.

En un programa en Pascal no hay necesidad de conservar una estructura tal que aparezca cada orden en una línea distinta. Se suele hacer así por claridad, pero realmente son los puntos y coma (cuando son necesarios) lo que indica el final de una orden, por lo que el programa anterior se podría haber escrito:

program Saludo; begin write('Hola') end.

Una última observación: si se compila este programa desde Turbo Pascal 5.0 o una versión superior, aparentemente "no pasa nada". No es así, sino que se ejecuta y se vuelve al editor tan rápido que no nos da tiempo a verlo. La solución es pulsar Alt+F5 para que nos muestre la pantalla del DOS.

Tema 2. Introducción al manejo de variables.

Las variables son algo que no contiene un valor predeterminado, una posición de memoria a la que nosotros asignamos un nombre y en la que podremos almacenar datos.

En el primer ejemplo que vimos, puede que no nos interese escribir siempre el mensaje "Hola", sino uno más personalizado según quien ejecute el programa. Podríamos preguntar su nombre al usuario, guardarlo en una variable y después escribirlo a continuación de la palabra "Hola", con lo que el programa quedaría

program Saludo2;

var nombre: string;

begin writeln('Introduce tu nombre, por favor'); readln(nombre); write('Hola ',nombre); end.

Aquí ya aparecen más conceptos nuevos. En primer lugar, hemos definido una variable, para lo que empleamos la palabra var, seguida del nombre que vamos a dar a la variable, y del tipo de datos que va a almacenar esa variable.

462

Page 463: Programacion en Java

Los nombres de las variables siguen las reglas que ya habíamos mencionado para los identificadores en general.

Con la palabra string decimos que la variable nombre va a contener una cadena de caracteres (letras o números). Un poco más adelante, en esta misma lección, comentamos los principales tipos de datos que vamos a manejar.

Pasemos al cuerpo del programa. En él comenzamos escribiendo un mensaje de aviso. Esta vez se ha empleado "writeln", que es exactamente igual que "write", con la única diferencia de que después de visualizar el mensaje, el cursor (la posición en la que se seguiría escribiendo, marcada normalmente por una rayita o un cuadrado que parpadea) pasa a la línea siguiente, en vez de quedarse justo después del mensaje escrito.

Después se espera a que el usuario introduzca su nombre, que le asignamos a la variable "nombre", es decir, lo guardamos en una posición de memoria cualquiera, que el compilador ha reservado para nosotros, y que nosotros no necesitamos conocer (no nos hace falta saber que está en la posición 7245 de la memoria, por ejemplo) porque siempre nos referiremos a ella llamándola "nombre". De todo esto se encarga la orden "readln".

Finalmente, aparece en pantalla la palabra "Hola" seguida por el nombre que se ha introducido. Como se ve en el ejemplo, "writeln" puede escribir más de un dato, pero eso lo estudiaremos con detalle un poco más adelante...

Tipos básicos de datos

En Pascal debemos declarar las variables que vamos a usar. Esto puede parecer incómodo para quien ya haya trabajado en Basic, pero en la práctica ayuda a conseguir programas más legibles y más fáciles de corregir o ampliar. Además, evita los errores que puedan surgir al emplear variables incorrectas: si queremos usar "nombre" pero escribimos "nombe", la mayoría de las versiones del lenguaje Basic no indicarían un error, sino que considerarían que se trata de una variable nueva, que no tendría ningún valor, y normalmente se le asignaría un valor de 0 o de un texto vacío.

En Pascal disponemos de una serie de tipos predefinidos, y de otros que podemos crear nosotros para ampliar el lenguaje. Los primeros tipos que veremos son los siguientes:

• Byte. Es un número entero, que puede valer entre 0 y 255. El espacio que ocupa en memoria es el de 1 byte, como su propio nombre indica.

• Integer. Es un número entero con signo, que puede valer desde -32768 hasta 32767. Ocupa 2 bytes de memoria.

• Char. Representa a un carácter (letra, número o símbolo). Ocupa 1 byte.

• String. Es una cadena de caracteres, empleado para almacenar y representar mensajes de más de una letra (hasta 255). Ocupa 256 bytes. El formato en Pascal estándar (y en Turbo Pascal, hasta la versión 3.01) era string[n], donde n es la anchura máxima que queremos almacenar en esa cadena de caracteres (de 0 a 255), y entonces ocupará n+1 bytes en memoria. En las últimas versiones de Turbo Pascal podemos usar el formato "string[n]" o simplemente "string", que equivale a "string[255]", como aparecía en el ejemplo anterior.

463

Page 464: Programacion en Java

• Real. Es un numero real con signo. Puede almacenar números con valores entre 2.9e-39 y 1.7e38 (en notación científica, e5 equivale a multiplicar por 105), con 11 o 12 dígitos significativos, y que ocupan 6 bytes en memoria.

• Boolean. Es una variable lógica, que puede valer TRUE (verdadero) o FALSE (falso), y se usa para comprobar condiciones.

• Array. Se emplea para definir vectores o matrices. Se deberá indicar el índice inferior y superior, separados por dos puntos (..), así como el tipo de datos.

Ejemplo: un vector formado por 10 números enteros sería

vector: array[1..10] of integer

y una matriz de dimensiones 3x2 que debiera contener números reales:

matriz1: array[1..3,1..2] of real

Para mostrar en pantalla el segundo elemento del vector se usaría

write(vector[2]);

y para ver el elemento (3,1) de la matriz,

writeln(matriz1[3,1]);

• Record. La principal limitación de un array es que todos los datos que contiene deben ser del mismo tipo. Pero a veces nos interesa agrupar datos de distinta naturaleza, como pueden ser el nombre y la edad de una persona, que serían del tipo string y byte, respectivamente. Entonces empleamos los records o registros, que se definen indicando el nombre y el tipo de cada campo (cada dato que guardamos en el registro), y se accede a estos campos indicando el nombre de la variable y el del campo separados por un punto:

program Ejemplo_de_registro;

var

dato: record nombre: string[20];

464

Page 465: Programacion en Java

edad: byte; end;

begin dato.nombre:='José Ignacio'; dato.edad:=23; write('El nombre es ', dato.nombre ); write(' y la edad ', dato.edad, ' años.'); end.

La única novedad en la definición de la variable es la aparición de una palabra end después de los nombres de los campos, lo que indica que hemos terminado de enumerar éstos.

Ya dentro del cuerpo del programa, vemos la forma de acceder a estos campos, tanto para darles un valor como para imprimirlo, indicando el nombre de la variable a la que pertenecen, seguido por un punto. El conjunto := es la sentencia de asignación en Pascal, y quiere decir que la variable que aparece a su izquierda va a tomar el valor que está escrito a la derecha (por ejemplo, x := 2 ).

Puede parecer engorroso el hecho de escribir "dato." antes de cada campo. También hay una forma de solucionarlo: cuando vamos a realizar varias operaciones sobre los campos de un mismo registro (record), empleamos la orden with, con la que el programa anterior quedaría

program Ejemplo_de_registro;

var dato: record nombre: string[20]; edad: byte; end;

begin with dato do begin nombre:='José Ignacio'; edad:=23; write('El nombre es ',nombre); write(' y la edad ',edad,' años.'); end; end.

En este caso tenemos un nuevo bloque en el cuerpo del programa, delimitado por el "begin" y el "end" situados más a la derecha, y equivale a decir "en toda esta parte del programa me estoy refiriendo a la variable dato". Así, podemos nombrar los campos que queremos modificar o escribir, sin necesidad de repetir a qué variable pertenecen.

465

Page 466: Programacion en Java

Ejemplos.

Ejemplo 1: Cambiar el valor de una variable.

program NuevoValor;

var numero: integer;

begin numero := 25; writeln('La variable vale ', numero); numero := 50; writeln('Ahora vale ', numero); numero := numero + 10; writeln('Y ahora ', numero); writeln('Introduce ahora tú el valor'); readln( numero ); writeln('Finalmente, ahora vale ', numero);end.

Ejemplo 2: Sumar dos números enteros.program SumaDosNumeros;

var numero1, numero2, suma: integer;

begin writeln('Introduce el primer número'); readln( numero1 ); writeln('Introduce el segundo número'); readln( numero2 ); suma := numero1 + numero2; writeln('La suma de los dos números es: ', suma);end.Ejemplo 3: Media de los elementos de un vector.

Este es un programa nada optimizado, para que se adapte a los conocimientos que tenemos por ahora y se vea cómo se manejan los Arrays. Admite muchas mejoras, que iremos viendo más adelante.

Como novedades sobre la lección, incluye la forma de dejar una línea de pantalla en blanco (con writeln), o de definir de una sola vez varias variables que sean del mismo tipo, separadas por comas. Las operaciones matemáticas se verán con más detalle en la próxima lección.

program MediadelVector;

var vector: array [1..5] of real; suma, media: real;begin writeln('Media de un vector con 5 elementos.'); writeln; writeln('Introduce el primer elemento'); readln(vector[1]); writeln('Introduce el segundo elemento');

466

Page 467: Programacion en Java

readln(vector[2]); writeln('Introduce el tercer elemento'); readln(vector[3]); writeln('Introduce el cuarto elemento'); readln(vector[4]); writeln('Introduce el quinto elemento'); readln(vector[5]); suma := vector[1] + vector[2] + vector[3] + vector[4] + vector[5]; media := suma / 5; writeln('La media de sus elementos es: ', media);end.Como todavía llevamos pocos conocimientos acumulados, la cosase queda aquí, pero con la siguiente lección ya podremosrealizar operaciones matemáticas algo más serias, y comparacioneslógicas.

Tema 3. Entrada/Salida básica.

Ya hemos visto por encima las dos formas más habituales de mostrar datos en pantalla, con "write" o "writeln", y de aceptar la introducción de datos por parte del usuario, con "readln" (o "read", que no efectúa un retorno de carro después de leer los datos). Veamos ahora su manejo y algunas de sus posibilidades con más detalle:

Para mostrar datos, tanto en pantalla como en impresora, se emplean write y writeln. La diferencia entre ambos es que "write" deja el cursor en la misma línea, a continuación del texto escrito, mientras que "writeln" baja a la línea inferior. Ambas órdenes pueden escribir tipos casi de cualquier clase: cadenas de texto, números enteros o reales, etc. No podremos escribir directamente arrays, records, ni muchos de los datos definidos por el usuario.

Cuando se desee escribir varias cosas en la misma línea, todas ellas se indican entre un mismo paréntesis, y separadas por comas.

Se puede especificar la anchura de lo escrito, mediante el símbolo de dos puntos (:) y la cifra que indique la anchura. Si se trata de un número real y queremos indicar también el número de decimales, esto se hace también después de los dos puntos, con el formato ":anchura_total:decimales". Como ejemplos:

write ('Hola, ',nombre,' ¿qué tal estás?'); writeln (resultado:5:2); writeln('Hola,',nombre:10,'. Tu edad es:',edad:2);

En el caso de una cadena de texto, la anchura que se indica es la que se tomará como mínima: si el texto es mayor no se "parte", pero si es menor, se rellena con espacios por la izquierda hasta completar la anchura deseada.

Igual ocurre con los números: si es más grande que la anchura indicada, no se "parte", sino que se escribe completo. Si es menor, se rellena con espacios por la izquierda. Los decimales sí que se redondean al número de posiciones indicado:

var num: real; begin num := 1234567.89; writeln(num); (* La línea anterior lo escribe con el formato por defecto:

467

Page 468: Programacion en Java

exponencial *) writeln(num:20:3); (* Con tres decimales *) writeln(num:7:2); (* Con dos decimales *) writeln(num:4:1); (* Con un decimal *) writeln(num:3:0); (* Sin decimales *) writeln(num:5); (* ¿Qué hará ahora? *) end.

La salida por pantalla de este programa sería:

1.2345678900E+06 1234567.890

.ej1234567.89

.ej1234567.9

.ej1234568

1.2E+06

Aquí se puede observar lo que ocurre en los distintos casos:

• Si no indicamos formato, se usa notación científica (exponencial). • Si la anchura es mayor, añade espacios por la izquierda. • Si es menor, no se trunca el número. • Si el número de decimales es mayor, se añaden ceros. • Si éste es menor, se redondea. • Si indicamos formato pero no decimales, sigue usando notación exponencial, pero lo más

compacta que pueda, tratando de llegar al tamaño que le indicamos.

En este programa ha aparecido también otra cosa nueva: los comentarios. Un comentario es algo que no se va a ejecutar, y que nosotros incluimos dentro del programa para que nos resulte más legible o para aclarar lo que hace una línea o un conjunto de líneas.

En Pascal, los comentarios se encierran entre (* y *). También está permitido usar { y }, tanto en Turbo Pascal como en SURPAS. Como se ve en el ejemplo, pueden ocupar más de una línea.

En la práctica, es muy importante que un programa esté bien documentado. Cuando se trabaja en grupo, la razón es evidente: a veces es la única forma de que los demás entiendan nuestro trabajo. En estos casos, el tener que dar explicaciones "de palabra" es contraproducente: Se pierde tiempo, las cosas se olvidan... Tampoco es cómodo distribuir las indicaciones en ficheros aparte, que se suelen extraviar en el momento más inoportuno. Lo ideal es que los comentarios aclaratorios estén siempre en el texto de nuestro programa.

Pero es que cuando trabajamos solos también es importante, porque si releemos un programa un mes después de haberlo escrito, lo habitual es que ya no nos acordemos de lo que hacía la variable X, de por qué la habíamos definido como "Record" y no como "Array", por qué dejábamos en blanco la primera ficha o por qué empezábamos a ordenar desde atrás.

468

Page 469: Programacion en Java

Para tomar datos del usuario, la forma más directa es empleando readln, que toma un texto o un número y asigna este valor a una variable. No avisa de lo que está haciendo, así que normalmente convendrá escribir antes en pantalla un mensaje que indique al usuario qué esperamos que teclee:

writeln('Por favor, introduzca su nombre'); readln(nombre);

"Readln" tiene algunos inconvenientes:

• No termina hasta que pulsemos RETURN. • La edición es incómoda: para corregir un error sólo podemos borrar todo lo que habíamos

escrito desde entonces, no podemos usar las flechas o INICIO/FIN para desplazarnos por el texto.

• Si queremos dar un valor a una variable numérica y pulsamos " 23" (un espacio delante del número) le dará un valor 0.

• ...

Más adelante, veremos que existen formas mucho más versátiles y cómodas de leer datos a través del teclado, en el mismo tema en el que veamos cómo se maneja la pantalla en modo texto desde Pascal...

Tema 4. Operaciones matemáticas.

En Pascal contamos con una serie de operadores para realizar sumas, restas, multiplicaciones y otras operaciones no tan habituales.

En operaciones como +, - y * no debería haber ninguna duda. Los problemas pueden venir con casos como el de 10/3. Si 10 y 3 son números enteros, ¿qué ocurre con su división? En otros lenguajes como C, el resultado sería 3, la parte entera de la división. En Pascal no es así: el resultado sería 3.333333, un número real. Si queremos la parte entera de la división, deberemos utilizar div. Finalmente, mod nos indica cual es el resto de la división. El signo - se puede usar también para indicar negación. Allá van unos ejemplillos:

program operaciones;

var e1, e2: integer; (* Números enteros *) r1, r2, r3: real; (* Números reales *)

begin e1:=17; e2:=5; r1:=1; r2:=3.2; writeln('Empezamos...'); r3:=r1+r2; writeln('La suma de r1 y r2 es :', r3); writeln(' o también ', r1+r2 :5:2); (* Indicando el formato *) writeln('El producto de r1 y r2 es :', r1 * r2); writeln('El valor de r1 dividido entre r2 es :', r1 / r2); writeln('La diferencia de e2 y e1 es : ', e2 - e1); writeln('La división de e1 entre e2 : ', e1 / e2);

469

Page 470: Programacion en Java

writeln(' Su división entera : ', e1 div e2); writeln(' Y el resto de la división : ', e1 mod e2); writeln('El opuesto de e2 es :', -e2); end.

El operador + (suma) se puede utilizar también para concatenar cadenas de texto, así:

var texto1, texto2, texto3: string;

begin texto1 := 'Hola '; texto2 := '¿Cómo estás?'; texto3 := texto1 + texto2; writeln(texto3); (* Escribirá "Hola ¿Cómo estás?" *) end.

Cuando tratemos tipos de datos más avanzados, veremos que +, - y * también se pueden utilizar para conjuntos, e indicarán la unión, diferencia e intersección.

Operadores lógicos

Vimos de pasada que en el tema que había unos tipos de datos llamados "boolean", y que podían valer TRUE (verdadero) o FALSE (falso). En la próxima lección veremos cómo hacer comparaciones del estilo de "si A es mayor que B y B es mayor que C", y empezaremos a utilizar variables de este tipo, pero vamos a mencionar ya eso del "y".

Podremos encadenar proposiciones de ese tipo (si A y B entonces C) con: and (y), or (ó), not (no) y los operadores relacionales, que se usan para comparar y son los siguientes:

Operador Operación

= Igual a <> No igual a (distinto de) < Menor que > Mayor que <= Menor o igual que >= Mayor o igual que

Igual que antes, algunos de ellos (>=, <=, in) los utilizaremostambién en los conjuntos, más adelante.

Los operadores "and", "or" y "not", junto con otros, se pueden utilizar también para operaciones entre bits de números enteros. Lo comento de pasada para no liar a los que empiezan. De momento, ahí va resumido y sin más comentarios. Quien quiera saber más, lo podrá ver en las ampliaciones al curso básico.

Operador Operación not Negación and Producto lógico or Suma lógica

470

Page 471: Programacion en Java

xor Suma exclusiva shl Desplazamiento hacia la izquierda shr Desplazamiento a la derecha

Queda como ejercicio hallar (y tratar de entender) el resultado de esteprogramita: begin writeln('Allá vamos... '); writeln( 5+3+4*5*2 ); writeln( (5+3)*4+3*5-8/2+7/(3-2) ); writeln( 5 div 3 + 23 mod 4 - 4 * 5 ); writeln( 125 and 6 ); (* Este para los más osados *) end.

Tema 5: Condiciones.

Vamos a ver cómo podemos evaluar condiciones desde Pascal. La primera construcción que trataremos es if ... then. En español sería "si ... entonces", que expresa bastante bien lo que podemos hacer con ella. El formato es "if condición then sentencia". Veamos un ejemplo breve antes de seguir:

program if1;

var numero: integer;

begin writeln('Escriba un número'); readln(numero); if numero>0 then writeln('El número es positivo'); end.

La "condición" debe ser una expresión que devuelva un valor del tipo "boolean" (verdadero/falso). La sentencia se ejecutará si ese valor es "cierto" (TRUE). Este valor puede ser tanto el resultado de una comparación como la anterior, como una propia variable booleana. Así, una forma más "rebuscada" (pero que a veces resultará más cómoda y más legible) de hacer lo anterior sería:

program if2;

var numero: integer; esPositivo: boolean;

begin writeln('Escriba un número'); readln(numero); esPositivo := (numero>0); if esPositivo then writeln('El número es positivo'); end.

471

Page 472: Programacion en Java

Cuando veamos en el próximo tema las órdenes para controlar el flujo del programa, seguiremos descubriendo aplicaciones de las variables booleanas, que muchas veces uno considera "poco útiles" cuando está aprendiendo.

La "sentencia" puede ser una sentencia simple o una compuesta. Las sentencias compuestas se forman agrupando varias simples entre un "begin" y un "end":

program if3;

var numero: integer;

begin writeln('Escriba un número'); readln(numero); if numero<0 then begin writeln('El número es negativo. Pulse INTRO para seguir.'); readln end; end.

En este ejemplo, si el número es negativo, se ejecutan dos acciones: escribir un mensaje en pantalla y esperar a que el usuario pulse INTRO (o ENTER, o RETURN, o <-+, según sea nuestro teclado), lo que podemos conseguir usando "readln" pero sin indicar ninguna variable en la que queremos almacenar lo que el usuario teclee.

También podemos indicar lo que queremos que se haga si no se cumple la condición. Para ello tenemos la construcción if condición then sentencia1 else sentencia2:

program if4;

var numero: integer;

begin writeln('Escriba un número'); readln(numero); if numero<0 then writeln('El número es negativo.') else writeln('El número es positivo o cero.') end.

Un detalle importante que conviene tener en cuenta es que antes del "else" no debe haber un punto y coma, porque eso indicaría el final de la sentencia "if...", y el compilador nos avisaría con un error.

472

Page 473: Programacion en Java

Las sentencias "if...then...else" se pueden encadenar:

program if5;

var numero: integer;

begin writeln('Escriba un número'); readln(numero); if numero<0 then writeln('El número es negativo.') else if numero>0 then writeln('El número es positivo.') else writeln('El número es cero.') end.

Si se deben cumplir varias condiciones a la vez, podemos enlazarlas con "and" (y). Si se pueden cumplir varias, usaremos "or" (o). Para negar, "not" (no):

if ( opcion = 1 ) and ( terminado = true ) then [...] if ( opcion = 3 ) or ( teclaPulsada = true ) then [...] if not ( preparado ) then [...] if ( opcion = 2 ) and not ( nivelDeAcceso < 40 ) then [...]

Pero cuando queremos comprobar entre varios posibles valores, sería muy pesado tener que hacerlo con muchos "if" seguidos o encadenar muchos con "and" u "or". Hay una alternativa que resulta mucho más cómoda: la orden case. Su sintaxis es

case expresión of caso1: sentencia1; caso2: sentencia2; ... casoN: sentenciaN; end;

o bien, si queremos indicar lo que se debe hacer si no coincide con ninguno de los valores que hemos enumerado, usamos else:

case expresión of caso1: sentencia1; caso2: sentencia2; ... casoN: sentenciaN; else otraSentencia; end;

En Pascal estándar, esta construcción se empleaba con otherwise en lugar de "else" para significar "en caso contrario", así que si alguien no usa TP/BP, sino un compilador que protesta con el "else", sólo tiene que probar con "otherwise".

473

Page 474: Programacion en Java

Con un ejemplo se verá más claro cómo usar "case":

program case1;

var letra: char;

begin WriteLn('Escriba un letra'); ReadLn(letra); case letra of ' ': WriteLn('Un espacio'); 'A'..'Z', 'a'..'z': WriteLn('Una letra'); '0'..'9': WriteLn('Un dígito'); '+', '-', '*', '/': WriteLn('Un operador'); else WriteLn('No es espacio, ni letra, ni dígito, ni operador'); end; end.

Como último comentario: la "expresión" debe pertenecer a un tipo de datos con un número finito de elementos, como "integer" o "char", pero no "real".

Y como se ve en el ejemplo, los "casos" posibles pueden ser valores únicos, varios valores separados por comas, o un rango de valores separados por .. (como los puntos suspensivos, pero sólo dos).

Tema 6: Bucles.

Vamos a ver cómo podemos crear bucles, es decir, partes del programa que se repitan un cierto número de veces.

Según cómo queramos que se controle ese bucle, tenemos tres posibilidades, que vamos a empezar a ver ya por encima:

• for..to: La orden se repite desde que una variable tiene un valor inicial hasta que alcanza otro valor final (un cierto NUMERO de veces).

• while..do: Repite una sentencia MIENTRAS que sea cierta la condición que indicamos. • repeat..until: Repite un grupo de sentencias HASTA que se dé una condición.

La diferencia entre estos dos últimos es que "while" comprueba la condición antes de ejecutar las otras sentencias, por lo que puede que estas sentencias ni siquiera se lleguen a ejecutar, si la condición de entrada es falsa. En "repeat", la condición se comprueba al final, de modo que las sentencias intermedias se ejecutarán al menos una vez.

Vamos a verlos con más detalle...

For.

El formato de "for" es

474

Page 475: Programacion en Java

for variable := ValorInicial to ValorFinal do Sentencia;

Vamos a ver algunos ejemplos.

Primero, un miniprograma que escriba los números del uno al diez:

var contador: integer;

begin for contador := 1 to 10 do writeln( contador ); end.

Los bucles "for" se pueden enlazar uno dentro de otro, de modo que un segundo ejemplo que escribiera las tablas de multiplicar del 1 al 5 podría ser

var tabla, numero: integer;

begin for tabla := 1 to 5 do for numero := 1 to 10 do writeln( tabla, 'por ', numero ,'es', tabla * numero ); end.

Hasta ahora hemos visto sólo casos en los que después de "for" había un única sentencia. ¿Qué ocurre si queremos repetir más de una orden? Basta encerrarlas entre "begin" y "end" para convertirlas en una sentencia compuesta.

Así, vamos a mejorar el ejemplo anterior haciendo que deje una línea en blanco entre tabla y tabla:

var tabla, numero: integer;

begin for tabla := 1 to 5 do begin for numero := 1 to 10 do writeln( tabla, 'por ', numero ,'es', tabla * numero ); writeln; (* Línea en blanco *) end; end.

475

Page 476: Programacion en Java

Conviene recordar que es muy conveniente usar la escritura indentada, que en este caso ayuda a ver dónde empieza y termina lo que hace cada "for".

Una observación: para "contar" no necesariamente hay que usar números:

var letra: char;

begin for letra := 'a' to 'z' do write( letra ); end.

Como último comentario: con el bucle "for", tal y como lo hemos visto, sólo se puede contar en forma creciente y de uno en uno. Para contar de forma decreciente, se usa "downto" en vez de "to".

Para contar de dos en dos (por ejemplo), hay usar "trucos": multiplicar por dos o sumar uno dentro del cuerpo del bucle, etc... Eso sí, sin modificar la variable que controla el bucle (usar cosas como "write(x*2)" en vez de "x := x*2", que pueden dar problemas en algunos compiladores).

Como ejercicios propuestos:

1. Un programa que escriba los números 2, 4, 6, 8 ... 16. 2. Otro que escriba 6, 5, 4,..., 1. 3. Otro que escriba 3, 5, 7,..., 21. 4. Otro que escriba 12, 10, 8,..., 0. 5. Otro que multiplique dos matrices. 6. Para los que conozcan el problema, uno de resolución de sistemas de ecuaciones por

Gauss.

While.

Vimos como podíamos crear estructuras repetitivas con la orden "for", y comentamos que se podía hacer también con "while..do", comprobando una condición al principio, o con "repeat..until", comprobando la condición al final de cada repetición. Vamos a verlas con más detalle:

La sintaxis de "while"

while condición do sentencia;

Se podría traducir como "MIENTRAS condición HAZ sentencia", o sea, que la sentencia se va a repetir mientras la condición sea cierta.

Un ejemplo que nos diga la longitud de todas las frases que queramos es:

var

476

Page 477: Programacion en Java

frase: string;

begin writeln('Escribe frases, y deja una línea en blanco para salir'); write( '¿Primera frase?' ); readln( frase ); while frase <> '' do begin writeln( 'Su longitud es ', length(frase) ); write( '¿Siguiente frase?' ); readln( frase ) end end.

En el ejemplo anterior, sólo se entra al bloque begin-end (una sentencia compuesta) si la primera palabra es correcta (no es una línea en blanco). Entonces escribe su longitud, pide la siguiente frase y vuelve a comprobar que es correcta.

Como comentario casi innecesario, length es una función que nos dice cuantos caracteres componen una cadena de texto.

Si ya de principio la condición es falsa, entonces la sentencia no se ejecuta ninguna vez, como pasa en este ejemplo:

while (2<1) do writeln('Dos es menor que uno');

Repeat .. Until.

Para "repeat..until", la sintaxis es

repeat sentencia; ... sentencia; sentencia until condición;

Es decir, REPITE un grupo de sentencias HASTA que la condición sea cierta. Cuidado con eso: es un grupo de sentencias, no sólo una, como ocurría en "while", de modo que ahora no necesitaremos "begin" y "end" para crear sentencias compuestas.

El conjunto de sentencias se ejecutará al menos una vez, porque la comprobación se realiza al final.

477

Page 478: Programacion en Java

Como último detalle, de menor importancia, no hace falta terminar con punto y coma la sentencia que va justo antes de "until", al igual que ocurre con "end".

Un ejemplo clásico es la "clave de acceso" de un programa, que iremos mejorando cuando veamos distintas formas de "esconder" lo que se teclea, bien cambiando colores o bien escribiendo otras letras, como *.

program ClaveDeAcceso;

var ClaveCorrecta, Intento: String;

begin ClaveCorrecta := 'PascalForever'; repeat WriteLn( 'Introduce la clave de acceso...' ); ReadLn( Intento ) until Intento = ClaveCorrecta (* Aquí iría el resto del programa *) end.

Como ejercicios propuestos:

1. Mejorar el programa de la clave de acceso para que avise de que la clave no es correcta. 2. Mejorar más todavía para que sólo haya tres intentos. 3. Adaptar la primera versión (el ejemplo), la segunda (la mejorada) y la tercera (re-mejorada)

para que empleen "while" y no "until"

Por cierto, si alguien ha programado en Basic puede que se pregunte por la orden goto. Existe en Pascal, pero su uso va a en contra de todos los principios de la Programación Estructurada, solo está "medio-permitido" en casos muy concretos, así que lo veremos más adelante.

Soluciones a los ejercicios.

Vamos a empezar a resolver los ejercicios que habíamos ido proponiendo.

Antes que nada, hay que puntualizar una cosa: el que así se resuelva de una forma no quiere decir que no se pueda hacer de otras. Ni siquiera que la mía sea la mejor, porque trataré de adaptarlos al nivel que se supone que tenemos.

1.- Un programa que escriba los números 2, 4, 6, 8 ... 16.

program del2al16;

var i: integer;

478

Page 479: Programacion en Java

begin for i := 1 to 8 do writeln( i*2 ); end.

2.- Otro que escriba 6, 5, 4,..., 1.

program del6al1;

var i: integer;

begin for i := 6 downto 1 do writeln( i ); end.

3.- Otro que escriba 3, 5, 7,..., 21.

program del3al21;

var i: integer;

begin for i := 1 to 10 do writeln( i*2 +1 ); end.

4.- Otro que escriba 12, 10, 8,..., 0.

program del12al0;

var i: integer;

begin for i := 6 downto 0 do writeln( i*2 ); end.

5.- Otro que multiplique dos matrices.

479

Page 480: Programacion en Java

Saltamos la parte que declara las variables y que pide los datos. La parte de la multiplicación sería, para matrices cuadradas de 10 por 10, por ejemplo:

for i := 1 to 10 do for j := 1 to 10 do c[i,j] := 0; (* limpia la matriz destino *)

for i :=1 to 10 do for j := 1 to 10 do for k := 1 to 10 do c[i,j] := c[i,j] + a[k,j] * b[i,k];

6.- Resolución de sistemas de ecuaciones por Gauss.

La única dificultad es conocer el problema: basta hacer 0 por debajo de la diagonal principal (y encima también, si se quiere, pero no es necesario) e ir despejando cada variable.

7.- Mejorar el programa de la clave de acceso para que avise de que la clave no es correcta.

program ClaveDeAcceso2;

var ClaveCorrecta, Intento: String;

begin ClaveCorrecta := 'PascalForever'; repeat WriteLn( 'Introduce la clave de acceso...' ); ReadLn( Intento ) if Intento <> ClaveCorrecta then writeln( ' Esa no es la clave correcta! '); until Intento = ClaveCorrecta (* Aquí iría el resto del programa *) end.

2.- Mejorar más todavía para que sólo haya tres intentos.

program ClaveDeAcceso3;

var ClaveCorrecta, Intento: String; NumIntento: integer; (* número de intento *)

480

Page 481: Programacion en Java

begin ClaveCorrecta := 'PascalForever'; NumIntento := 0; (* aún no hemos probado *) repeat NumIntento := NumIntento + 1; (* siguiente intento *) WriteLn( 'Introduce la clave de acceso...' ); ReadLn( Intento ) if Intento <> ClaveCorrecta then begin writeln( ' Esa no es la clave correcta! '); if NumIntentos = 3 then exit (* sale si es el 3º *) end until Intento = ClaveCorrecta (* Aquí iría el resto del programa *) end.

3.- Adaptar la primera versión (el ejemplo), la segunda (la mejorada) y la tercera (re-mejorada) para que empleen "while" y no "repeat-until"

Sólo ponemos una de ellas, porque las demás son muy similares.

program ClaveDeAcceso4; (* equivale a ClaveDeAcceso2*)

var ClaveCorrecta, Intento: String;

begin ClaveCorrecta := 'PascalForever'; Intento := ''; (* cadena vacía *) while Intento <> ClaveCorrecta do (* mientras no acertemos *) begin WriteLn( 'Introduce la clave de acceso...' ); ReadLn( Intento ) if Intento <> ClaveCorrecta then writeln( ' Esa no es la clave correcta! '); end; (* fin del "while" *) (* Aquí iría el resto del programa *) end.

Tema 7: Constantes y tipos.

Definición de constantes.

Cuando desarrollamos un programa, nos podemos encontrar con que hay variables que realmente "no varían" a lo largo de la ejecución de un programa, sino que su valor es constante.

481

Page 482: Programacion en Java

Hay una manera especial de definirlas, que es con el especificador "const", que tiene el formato

const Nombre = Valor;

Veamos un par de ejemplos antes de seguir

const MiNombre = 'Nacho Cabanes'; const PI = 3.1415926535; const LongitudMaxima = 128;

Estas constantes se manejan igual que variables como las que habíamos visto hasta hora, sólo que no se puede cambiar su valor. Así, es valido hacer

Writeln(MiNombre); if Longitud > LongitudMaxima then ... OtraVariable := MiNombre; LongCircunf := 2 * PI * r;

pero no podríamos hacer

PI := 3.14; MiNombre := 'Nacho'; LongitudMaxima := LongitudMaxima + 10;

Las constantes son mucho más prácticas de lo que puede parecer a primera vista (especialmente para quien venga de lenguajes como Basic, en el que no existen -en el Basic "de siempre", puede que sí exista en los últimas versiones del lenguaje). Me explico con un ejemplo :

Supongamos que estamos haciendo nuestra agenda en Pascal (ya falta menos para que sea verdad), y estamos tan orgullosos de ella que queremos que en cada pantalla de cada parte del programa aparezca nuestro nombre, el del programa y la versión actual. Si lo escribimos de nuevas cada vez, además de perder tiempo tecleando más, corremos el riesgo de que un día queramos cambiar el nombre (ya no se llamará "Agenda" sino "SuperAgenda") pero lo hagamos en unas partes sí y en otras no, etc., y el resultado tan maravilloso quede estropeado por esos "detalles".

O si queremos cambiar la anchura de cada dato que guardamos de nuestros amigos, porque el espacio para el nombre nos había quedado demasiado escaso, tendríamos que recorrer todo el programa de arriba a abajo, con los mismos problemas, pero esta vez más graves aún, porque puede que intentemos grabar una ficha con una tamaño y leerla con otro distinto...

La solución será definir todo ese tipo de datos como constantes al principio del programa, de modo que con un vistazo a esta zona podemos hacer cambios globales:

const

482

Page 483: Programacion en Java

Nombre = 'Nacho'; Prog = 'SuperAgenda en Pascal'; Versión = 1.95;

LongNombre = 40; LongTelef = 9; LongDirec = 60; ...

Las declaraciones de las constantes se hacen antes del cuerpo del programa principal, y generalmente antes de las declaraciones de variables:

program MiniAgenda;

const NumFichas = 50;

var Datos: array[ 1..NumFichas ] of string;

begin ...

El identificador "const" tiene también en Turbo Pascal otro uso menos habitual: definir lo que se llaman constantes con tipo, que son variables normales y corrientes, pero a las que damos un valor inicial antes de que comience a ejecutarse el programa. Se usa

const variable: tipo = valor;

Así, volviendo al ejemplo de la clave de acceso, podíamos tener una variables "intentos" que dijese el número de intentos. Hasta ahora habríamos hecho

var intentos: integer;

begin intentos := 3; ...

483

Page 484: Programacion en Java

Ahora ya sabemos que sería mejor hacer, si sabemos que el valor no va a cambiar:

const intentos = 3;

begin ...

Pero si se nos da el caso de que vemos por el nombre que es alguien de confianza, que puede haber olvidado su clave de acceso, quizá nos interese permitirle 5 o más intentos. Ya no podemos usar "const" porque el valor puede variar, pero por otra parte, siempre comenzamos concediendo 3 intentos, hasta comprobar si es alguien de fiar. Podemos hacer

const intentos: integer = 3;

begin ...

Recordemos que una "constante con tipo" se manejará exactamente igual que una variable, con las ventajas de que está más fácil de localizar si queremos cambiar su valor inicial y de que el compilador optimiza un poco el código, haciendo el programa unos bytes más pequeño.

Definición de tipos.

El tipo de una variable es lo que indicamos cuando la declaramos:

var PrimerNumero: integer;

indica que vamos a usar una variable que se va a llamar PrimerNumero y que almacenará valores de tipo entero. Si queremos definir una de las fichas de lo que será nuestra agenda, también haríamos:

var ficha: record nombre: string; direccion: string; edad: integer; observaciones: string end;

Tampoco hay ningún problema con esto, ¿verdad? Y si podemos utilizar variables creando los tipos "en el momento", como en el caso anterior, ¿para qué necesitamos definir tipos? Vamos a verlo con un ejemplo. Supongamos que vamos a tener ahora dos variables: una "ficha1" que

484

Page 485: Programacion en Java

contendrá el dato de la ficha actual y otra "ficha2" en la que almacenaremos datos temporales. Veamos qué pasa...

program PruebaTipos;

var

ficha1: record nombre: string; direccion: string; edad: integer; observaciones: string end;

ficha2: record nombre: string; direccion: string; edad: integer; observaciones: string end;

begin ficha1.nombre := 'Pepe'; ficha1.direccion := 'Su casa'; ficha1.edad := 65; ficha1.observaciones := 'El mayor de mis amigos...'; ficha2 := ficha1; writeln( ficha2.nombre); end.

Veamos qué haría este programa: define dos variables que van a guardar la misma clase de datos. Da valores a cada uno de los datos que almacenará una de ellas. Después hacemos que la segunda valga lo mismo que la primera, e imprimimos el nombre de la segunda. Aparecerá escrito "Pepe" en la pantalla...

No. Aunque a nuestros ojos "ficha1" y "ficha2" sean iguales, para el compilador no es así, por lo que protesta y el programa ni siquiera llega a ejecutarse. Es decir: las hemos definido para que almacene la misma clase de valores, pero no son del mismo tipo.

Esto es fácil de solucionar:

var ficha1, ficha2: record nombre: string; direccion: string; edad: integer; observaciones: string end;

485

Page 486: Programacion en Java

begin ...

Si las definimos a la vez, SI QUE SON DEL MISMO TIPO. Pero surge un problema que se entenderá mejor más adelante, cuando empecemos a crear funciones y procedimientos. ¿Qué ocurre si queremos usar en alguna parte del programa otras variables que también sean de ese tipo? ¿Las definimos también a la vez? En muchas ocasiones no será posible.

Así que tiene que haber una forma de indicar que todo eso que sigue a la palabra "record" es un tipo al que nosotros queremos acceder con la misma comodidad que si fuese "integer" o "boolean", queremos definir un tipo, no simplemente declararlo, como estábamos haciendo.

Pues es sencillo:

type NombreDeTipo = DeclaracionDeTipo;

o en nuestro caso

type TipoFicha = record nombre: string; direccion: string; edad: integer; observaciones: string end;

var ficha1: TipoFicha; ... var ficha2: TipoFicha; ...

Ahora sí que podremos asignar valores entre variables que hayamos definido en distintas partes del programa, podremos usar esos tipos para crear ficheros (que también veremos más adelante), etc.

Tema 8: Procedimientos y funciones.

Conceptos básicos.

La programación estructurada trata de dividir el programa el bloques más pequeños, buscando una mayor legibilidad, y más comodidad a la hora de corregir o ampliar.

Por ejemplo, si queremos crear nuestra, podemos empezar a teclear directamente y crear un programa de 2000 líneas que quizás incluso funcione, o dividirlo en partes, de modo que el cuerpo del programa sea algo parecido a

486

Page 487: Programacion en Java

begin InicializaVariables; PantallaPresentacion; Repeat PideOpcion; case Opcion of '1': MasDatos; '2': CorregirActual; '3': Imprimir; ... end; Until Opcion = OpcionDeSalida; GuardaCambios; LiberaMemoria end.

Así resulta bastante más fácil de seguir.

En nuestro caso, estos bloques serán de dos tipos: procedimientos (procedure) y funciones (function).

La diferencia entre ellos es que un procedimiento ejecuta una serie de acciones que están relacionadas entre sí, y no devuelve ningún valor, mientras que la función sí que va a devolver valores. Veámoslo con un par de ejemplos:

procedure Acceso; var clave: string; (* Esta variable es local *) begin writeln(' Bienvenido a SuperAgenda '); writeln('=========================='); (* Para subrayar *) writeln; writeln; (* Dos líneas en blanco *) writeln('Introduzca su clave de acceso'); readln( clave ); (* Lee un valor *) if clave <> ClaveCorrecta then (* Compara con el correcto *) begin (* Si no lo es *) writeln('La clave no es correcta!'); (* avisa y *) exit (* abandona el programa *) end end;

Primeros comentarios sobre este ejemplo:

• El cuerpo de un procedimiento se encierra entre "begin" y "end", igual que las sentencias compuestas.

• Un procedimiento puede tener sus propias variables, que llamaremos variables locales, frente a las del resto del programa, que son globales. Desde dentro de un procedimiento podemos acceder a las variables globales (como ClaveCorrecta del ejemplo anterior), pero no podemos acceder a las locales desde fuera del procedimiento en el que las hemos definido.

487

Page 488: Programacion en Java

• La función exit, que no habíamos visto aún, permite interrumpir la ejecución del programa (o de un procedimiento) en un determinado momento.

Veamos el segundo ejemplo: una función que eleve un número a otro (no existe en Pascal), se podría hacer así, si ambos son enteros:

function potencia(a,b: integer): integer; (* a elevado a b *) var i: integer; (* para bucles *) temporal: integer; (* para el valor temporal *) begin temporal := 1; (* inicialización *) for i := 1 to b do temporal := temporal * a; (* hacemos "b" veces "a*a" *) potencia := temporal; (* y finalmente damos el valor *) end;

Comentemos cosas también:

• Esta función se llama "potencia". • Tiene dos parámetros llamados "a" y "b" que son números enteros (valores que "se le

pasan" a la función para que trabaje con ellos). • El resultado va a ser también un número entero. • "i" y "temporal" son variables locales: una para el bucle "for" y la otra almacena el valor

temporal del producto. • Antes de salir es cuando asignamos a la función el que será su valor definitivo.

Pero vamos a ver un programa que use esta función, para que quede un poco más claro:

program PruebaDePotencia;

var numero1, numero2: integer; (* Variable globales *)

function potencia(a,b: integer): integer; (* Definimos la función *) var i: integer; (* Locales: para bucles *) temporal: integer; (* y para el valor temporal *) begin temporal := 1; (* incialización *) for i := 1 to b do

488

Page 489: Programacion en Java

temporal := temporal * a; (* hacemos "b" veces "a*a" *) potencia := temporal; (* y finalmente damos el valor *) end;

begin (* Cuerpo del programa *) writeln('Potencia de un número entero'); writeln; writeln('Introduce el primer número'); readln( numero1 ); writeln('Introduce el segundo número'); readln( numero2 ); writeln( numero1 ,' elevado a ', numero2 ,' vale ', potencia (numero1, numero2) ) end.

Un procedimiento también puede tener "parámetros", igual que la función que acabamos de ver:

program ProcConParametros;

procedure saludo (nombre: string); (* Nuestro procedimiento *) begin writeln('Hola ', nombre, ' ¿qué tal estás?'); end;

begin (* Comienzo del programa *) writeln; (* Línea en blanco *) saludo( 'Eva' ); (* Saludamos a Eva *) end. (* Y se acabó *)

En el próximo apartado veremos la diferencia entre pasar parámetros por valor (lo que hemos estado haciendo) y por referencia (para poder modificarlos), y jugaremos un poco con la recursividad.

Pero antes, unos ejercicios propuestos de lo que hemos visto:

• Adaptar la función "potencia" que hemos visto para que trabaje con números reales, y permita cosas como 3.2 ^ 1.7

• Hacer una función que halle la raíz cúbica del número que se le indique. • Definir las funciones suma y producto de tres números y hacer un programa que haga una

operación u otra según le indiquemos (con "case", etc). • Un programa que halle la letra (NIF) que corresponde a un cierto DNI.

489

Page 490: Programacion en Java

Parámetros.

Ya habíamos visto, sin entrar en detalles, qué es eso de los parámetros: una serie de datos extra que indicábamos entre paréntesis en la cabecera de un procedimiento o función.

Es algo que estamos usando, sin saberlo, desde el primer tema, cuando empezamos a usar "WriteLn":

writeln( 'Hola' );

Esta línea es una llamada al procedimiento "WriteLn", y como parámetros le estamos pasando lo que queremos que escriba, en este caso el texto "Hola".

Pero vamos a ver qué ocurre si hacemos cosas como ésta:

program PruebaDeParametros;

var dato: integer;

procedure modifica( variable : integer); begin variable := 3 ; writeln( variable ); end;

begin dato := 2; writeln( dato ); modifica( dato ); writeln( dato ); end.

Vamos a ir siguiendo cada instrucción:

• Declaramos el nombre del programa. No hay problema. • Usaremos la variable "dato", de tipo entero. • El procedimiento "modifica" toma una variable de tipo entero, le asigna el valor 3 y la

escribe. Lógicamente, siempre escribirá 3. • Empieza el cuerpo del programa: damos el valor 2 a "dato". • Escribimos el valor de "dato". Todos de acuerdo en que será 2. • Llamamos al procedimiento "modifica", que asigna el valor 3 a "dato" y lo escribe. • Finalmente volvemos a escribir el valor de "dato"... ¿3?

490

Page 491: Programacion en Java

No: Escribe un 2. Las modificaciones que hagamos a "dato" dentro del procedimiento modifica sólo son válidas mientras estemos dentro de ese procedimiento. Lo que modificamos es la variable genérica que hemos llamado "variable", y que no existe fuera del procedimiento.

Eso es pasar un parámetro por valor. Si realmente queremos modificar el parámetro, lo que hacemos es simplemente añadir la palabra "var" delante de cada parámetro que queremos permitir que se pueda modificar. El programa quedaría:

program PruebaDeParametros2;

var dato: integer;

procedure modifica( var variable : integer); begin variable := 3 ; writeln( variable ); end;

begin dato := 2; writeln( dato ); modifica( dato ); writeln( dato ); end.

Esta vez la última línea del programa sí que escribe un 3 y no un 2, porque hemos permitido que los cambio hechos a la variable salgan del procedimiento. Esto es pasar un parámetro por referencia.

El nombre "referencia" alude a que no se pasa realmente al procedimiento o función el valor de la variable, sino la dirección de memoria en la que se encuentra, algo que más adelante llamaremos un "puntero".

Una de las aplicaciones más habituales de pasar parámetros por referencia es cuando una función debe devolver más de un valor. Habíamos visto que una función era como un procedimiento, pero además devolvía un valor (pero sólo uno). Si queremos obtener más de un valor de salida, una de las formas de hacerlo es pasándolos como parámetros, precedidos por la palabra "var".

Y como ejercicio queda un caso un poco más "enrevesado". Qué ocurre si el primer programa lo modificamos para que sea así:

program PruebaDeParametros3;

var dato: integer;

procedure modifica( dato : integer);

491

Page 492: Programacion en Java

begin dato := 3 ; writeln( dato ); end;

begin dato := 2; writeln( dato ); modifica( dato ); writeln( dato ); end.

Recursividad.

La idea en sí es muy sencilla: un procedimiento o función es recursivo si se llama a sí mismo. Para buscar un utilidad, vamos a verlo con un ejemplo clásico: el factorial de un número.

Partimos de la definición de factorial:

n! = n · (n-1) · (n-2) · ... · 3 · 2 · 1

Por otra parte,

(n-1)! = (n-1) · (n-2) · (n-3) · ... · 3 · 2 · 1

Luego podemos escribir cada factorial en función del factorial del siguiente número:

n! = n · (n-1)!

Acabamos de dar la definición recursiva del factorial. Ahora sólo queda ver cómo se haría eso programando:

program PruebaDeFactorial;

var numero: integer;

function factorial( num : integer) : integer; begin if num = 1 then factorial := 1 (* Aseguramos que tenga salida siempre *) else factorial := num * factorial( num-1 ); (* Caso general *) end;

begin writeln( 'Introduce un número entero (no muy grande) ' );

492

Page 493: Programacion en Java

readln(numero); writeln( 'Su factorial es ', factorial(numero) ); end.

Dos comentarios sobre este programa:

• Atención a la primera parte de la función recursiva: es muy importante comprobar que hay salida de la función, para que no se quede dando vueltas todo el tiempo y deje el ordenador colgado.

• No conviene poner números demasiado grandes. Los enteros van desde -32768 hasta 32767, luego si el resultado es mayor que este número, tendremos un desbordamiento y el resultado será erróneo. En cuanto a qué es "demasiado grande": el factorial de 8 es cerca de 40.000, luego sólo podremos usar números del 1 al 7. Si este límite del tamaño de los enteros parece preocupante, no hay por qué darle muchas vueltas, porque en el próximo tema veremos que hay otros tipos de datos que almacenan números más grandes o que nos permiten realizar ciertas cosas con más comodidad.

Un par de ejercicios:

- Una función recursiva que halle el producto de dos números enteros.

- Otra que halle la potencia (a elevado a b), también recursiva.

Soluciones.

Pues aquí van unas soluciones (insisto en que no tienen por qué ser las únicas ni las mejores) a los ejercicios propuestos en el tema 8:

1.- Adaptar la función "potencia" que hemos visto para que trabaje con números reales, y permita cosas como 3.2 ^ 1.7

Partimos del programa propuesto como ejemplo, que era:

function potencia(a,b: integer): integer; (* a elevado a b *) var i: integer; (* para bucles *) temporal: integer; (* para el valor temporal *) begin temporal := 1; (* inicialización *) for i = 1 to b do temporal := temporal * b; (* hacemos "b" veces "a*a" potencia := temporal; (* y finalmente damos el valor *) end;

493

Page 494: Programacion en Java

No basta con cambiar los tipos de las variables, poniendo "real" en vez de "integer". Si hacemos esto, ni siquiera podremos compilar el programa, porque una variable de tipo "real" no se puede usar para controlar un bucle.

Una forma de hacerlo es empleando números exponenciales y logaritmos: como son operaciones inversas, tenemos que exp( log( x )) = x, donde "log" y "exp" sí que son funciones predefinidas en Pascal. Pero, por otra parte, sabemos que log (a^b) = b · log a. Así, una forma (no la mejor) de hacerlo sería simplemente

function PotReal(a,b: real): real; (* a elevado a b, reales *) begin

PotReal := exp ( b * log ( a )) ; end;

2.- Hacer una función que halle la raíz cúbica del número que se le indique.

Mirando la función anterior, ya es fácil: una raíz cúbica es lo mismo que elevar a 1/3, así que se puede hacer;

function RaizCubica(n: real): real; begin RaizCubica := exp ( 0.33333333 * log ( n )) ; end;

3.- Definir las funciones suma y producto de tres números y hacer un programa que haga una operación u otra según le indiquemos (con "case", etc.).

Trabajando con números reales, sería:

program Suma_Y_Producto;

var num1, num2: real; (* Definición de variables *) opcion: char;

function suma( a,b : real): real; (* Esta es la función "suma" *) begin suma := a + b ; end;

function producto( a,b : real): real; (* y el producto *) begin producto := a * b ; end;

begin

494

Page 495: Programacion en Java

write( 'Introduzca el primer número: '); readln( num1 ); write( 'Introduzca el segundo número: '); readln( num2 ); writeln( '¿Qué operación desea realizar?' ); writeln( 's = Suma p = Producto' ); readln( opcion ); case opcion of 's': writeln( 'Su suma es ', suma(num1,num2) ); 'p': writeln( 'Su producto es ', producto(num1,num2) ); else writeln( 'Operación desconocida!' ); end; (* Fin del case *) end. (* y del programa *)

4.- Un programa que halle la letra que corresponde a un cierto DNI.

La forma de calcular la letra que corresponde a un cierto DNI es muy sencilla: basta con dividir el número del DNI entre 23 y coger el resto de esa división. Según el valor de ese resto, se asigna una letra u otra:

0=T 1=R 2=W 3=A 4=G 5=M 6=Y 7=F 8=P 9=D 10=X 11=B 12=N 13=J

14=Z 15=S 16=Q 17=V 18=H 19=L 20=C 21=K 22=E

Así, un programa que halle la letra (implementando esta parte como función, para que quede "más completo") puede ser simplemente:

program Nif; (* Letra del NIF. Nacho Cabanes, Jun. 92 *)

var numero:longint;

function LetraNif ( dni: longint ): char; const valores: string[24]='TRWAGMYFPDXBNJZSQVHLCKE'; begin LetraNif := valores [( dni mod 23 ) + 1]; end;

begin writeln('¿Cual es el DNI cuyo NIF quiere hallar?'); readln(numero); writeln('La letra es ', LetraNif( Numero ) ,'.'); end.

495

Page 496: Programacion en Java

5.- Qué ocurre si el primer programa del tema 8.2 lo modificamos para que sea así:

program PruebaDeParametros3;

var dato: integer;

procedure modifica( dato : integer); begin dato := 3 ; writeln( dato ); end;

begin dato := 2; writeln( dato ); modifica( dato ); writeln( dato ); end.

Sólo ha cambiado el nombre de la variable global, que ahora se llama "dato", igual que la local. Pero de cualquier modo, ambas variables SON DISTINTAS, de modo que el programa sigue funcionando igual que antes, cuando los nombres de las variables eran distintos.

Es aconsejable, para evitar problemas, no utilizar variables globales y locales con el mismo nombre.

6.- Hallar una función recursiva que halle el producto de dos números enteros positivos.

Este es un caso con poco sentido en la práctica, pero que se puede resolver simplemente recordando que un producto no son más que varias sumas:

function producto( a,b : integer) : integer; var temporal: integer; begin if b = 1 then producto := a else producto := a + producto (a, b-1 ); end;

Y un programa que la utilizase sería:

var num1, num2: integer;

begin write( 'Introduzca el primer número: '); readln( num1 ); write( 'Introduzca el segundo número: '); readln( num2 ); writeln( 'Su producto es ', producto(num1,num2) ); end.

496

Page 497: Programacion en Java

7.- Otra que halle la potencia (a elevado a b), también recursiva.

La idea es la misma que antes: simplificar el problema, esta vez escribiendo la potencia como varios productos:

function potencia( a,b : integer) : integer; var temporal: integer; begin if b = 1 then potencia := a else potencia := a * potencia (a, b-1 ); end;

Tema 9: Otros tipos de datos.

Comenzamos a ver los tipos de datos que podíamos manejar en el tema 2. En aquel momento tratamos los siguientes:

• Byte. Entero, 0 a 255. Ocupa 1 byte de memoria. • Integer. Entero con signo, -32768 a 32767. Ocupa 2 bytes. • Char. Carácter, 1 byte. • String[n]. Cadena de n caracteres (hasta 255). Ocupa n+1 bytes. • Real. Real con signo. De 2.9e-39 a 1.7e38, 11 o 12 dígitos significativos, ocupa 6 bytes. • Boolean. TRUE o FALSE. • Array. Vectores o matrices. • Record. Con campos de distinto tamaño.

Esta vez vamos a ampliar con otros tipos de datos que podemos encontrar en Turbo Pascal (aunque puede que no en otras versiones del lenguaje Pascal).

Como resumen previo, vamos a ver lo siguiente:

• Enteros. • Correspondencia byte-char. • Reales del 8087. • Tipos enumerados. • Más detalles sobre Strings. • Registros variantes. • Conjuntos.

Vamos allá:

497

Page 498: Programacion en Java

Comencemos por los demás tipos de números enteros. Estos son:

• Shortint. Entero con signo, de -128 a 127, ocupa 1 byte. • Word. Entero sin signo, de 0 a 65535. Ocupa 2 bytes. • Longint. Sin signo, de -2147483648..2147483647. Ocupa 4 bytes.

Estos tipos, junto con "char" (y "boolean" y otros para los que no vamos a entrar en tanto detalle) son tipos ordinales, existe una relación de orden entre ellos y cada elemento está precedido y seguido por otro que podemos conocer (cosa que no ocurre en los reales). Para ellos están definidas las funciones:

• pred - Predecesor de un elemento : pred(3) = 2 • succ - Sucesor: succ(3) = 4 • ord - Número de orden (posición) dentro de todo el conjunto.

El uso más habitual de "ord" es para convertir de "char" a "byte". Los caracteres se almacenan en memoria, de tal forma que a cada uno se le asigna un número entre 0 y 255, su "código ASCII" (ej: A=65, a=96, 0=48, Ó=224). La forma de hallar el código ASCII de una letra es simplemente ord(letra), como ord('Ó').

El paso contrario, la letra que corresponde a cierto número, se hace con la función "chr". Así, podemos escribir los caracteres "imprimibles" de nuestro ordenador sabiendo que van del 32 al 255 (los que están por debajo de 32 suelen ser caracteres de control, que en muchos casos no se podrán mostrar en pantalla):

var bucle: byte;

begin for bucle := 32 to 255 do write( chr(bucle) ); end.

Si tenemos coprocesador matemático, (vamos a suponer que no es el caso y no vamos a dar más detalles de su uso), podemos utilizar también los siguientes tipos de números reales del 8087:

Nombre ¦ Rango ¦ Dígitos ¦ Bytes ---------+--------------------------+---------+------ single ¦ 1.5e-45 a 3.4e38 ¦ 7-8 ¦ 4 double ¦ 5.0e-324 a 1.7e308 ¦ 15-16 ¦ 8 extended ¦ 3.4e-4932 a 1.1e4932 ¦ 19-20 ¦ 10 comp ¦ -9.2e18 a 9.2e18 ¦ 19-20 ¦ 8

498

Page 499: Programacion en Java

El tipo "comp" es un entero de 8 bytes, que sólo tenemos disponible si usamos el coprocesador.

Nosotros podemos crear nuestros propios tipos de datos enumerados:

type DiasSemana = (Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo);

Declaramos las variables igual que hacíamos con cualquier otro tipo:

var dia: DiasSemana

Y las empleamos como siempre: podemos darles un valor, utilizarlas en comparaciones, etc.

begin dia := Lunes; [...] if dia = Viernes then writeln( 'Se acerca el fin de semana!' ); [...]

Los tipos enumerados también son tipos ordinales, por lo que podemos usar pred, succ y ord con ellos. Así, el en ejemplo anterior

pred(Martes) = Lunes, succ(Martes) = Miercoles, ord(Martes) = 1

(el número de orden de Martes es 1, porque es el segundo elemento, y se empieza a numerar en cero).

Volvamos a los strings. Habíamos visto que se declaraban de la forma "string[n]" y ocupaban n+1 bytes (si escribimos sólo "string", es válido en las últimas versiones de Pascal y equivale a "string[255]"). Ocupa n+1 bytes porque también se guarda la longitud de la cadena (el espacio que ocupa realmente).

Vamos a ver cómo acceder a caracteres individuales de una cadena. La definición anterior, indicando el tamaño entre corchetes le recuerda a la de un Array. Así es. De hecho, la definición original en Pascal del tipo String[x] era "Packed Array[1..x] of char" ("packed" era para que el compilador intentase "empaquetar" el array, de modo que ocupase menos; esto no es necesario en Turbo Pascal). Así, con nombre[1] accederíamos a la primera letra del nombre, con nombre[2] a la segunda, y así sucesivamente.

499

Page 500: Programacion en Java

Una última curiosidad: habíamos dicho que se guarda también la longitud de la cadena. Esto se hace en la posición 0. Veamos un programa de ejemplo:

var linea: string [20]; (* Cadena inicial: limitada a 20 letras *) pos: byte; (* Posición que estamos mirando *)

begin writeln( 'Introduce una línea de texto...' ); readln( linea ); for pos := 1 to ord(linea[0]) do writeln(' La letra número ', pos,' es una ', linea[pos]); end.

Comentarios:

• "linea[0]" da la longitud de la cadena, pero es un carácter, luego debemos convertirlo a byte con "ord".

• Entonces, recorremos la cadena desde la primera letra hasta la última. • Si tecleamos más de 20 letras, las restantes se desprecian.

También habíamos visto ya los registros (records), pero con unos campos fijos. No tiene por qué ser necesariamente así. Tenemos a nuestra disposición los registros variantes, en los que con un "case" podemos elegir unos campos u otros. La mejor forma de entenderlos es con un ejemplo.

program RegistrosVariantes;

type TipoDato = (Num, Fech, Str); Fecha = record D, M, A: Byte; end;

Ficha = record Nombre: string[20]; (* Campo fijo *) case Tipo: TipoDato of (* Campos variantes *) Num: (N: real); (* Si es un número: campo N *) Fech: (F: Fecha); (* Si es fecha: campo F *) Str: (S: string); (* Si es string: campo S *) end;

var UnDato: Ficha;

begin UnDato.Nombre := 'Nacho'; (* Campo normal de un record *) UnDato.Tipo := Num; (* Vamos a almacenar un número *)

500

Page 501: Programacion en Java

UnDato.N := 3.5; (* que vale 3.5 *)

UnDato.Nombre := 'Nacho2'; (* Campo normal *) UnDato.Tipo := Fech; (* Ahora almacenamos una fecha *) UnDato.F.D := 7; (* Día: 7 *) UnDato.F.M := 11; (* Mes: 11 *) UnDato.F.A := 93; (* Año: 93 *)

UnDato.Nombre := 'Nacho3'; (* Campo normal *) UnDato.Tipo := Str; (* Ahora un string *) UnDato.S := 'Nada'; (* el texto "Nada" *) end.

Finalmente, tenemos los conjuntos (sets). Un conjunto está formado por una serie de elementos de un tipo base, que debe ser un ordinal de no más de 256 valores posibles, como un "char", un "byte" o un enumerado.

type Letras = set of Char;

type DiasSemana = (Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo);

Dias = set of DiasSemana;

Para construir un "set" utilizaremos los corchetes ([ ]), y dentro de ellos enumeramos los valores posibles, uno a uno o como rangos de valores separados por ".." :

var LetrasValidas : Letras; Fiesta : Dias;

begin LetrasValidas = ['a'..'z', 'A'..'z', '0'..'9', 'ñ', 'Ñ'] Fiesta = [ Sabado, Domingo ] end.

Un conjunto vacío se define con [ ]. Las operaciones que tenemos definidas sobre los conjuntos son:

501

Page 502: Programacion en Java

Operac ¦ Nombre ---------+---------------- + ¦ Unión - ¦ Diferencia * ¦ Intersección in ¦ Pertenencia

Así, podríamos hacer cosas como

VocalesPermitidas := LetrasValidas * Vocales;

if DiaActual in Fiesta then writeln( 'No me dirás que estás trabajando...' );

En el primer ejemplo hemos dicho que el conjunto de vocales permitidas (que deberíamos haber declarado) es la intersección de las vocales (que también debíamos haber declarado) y las letras válidas.

En el segundo, hemos comprobado si la fecha actual (que sería de tipo DiasSemana) pertenece al conjunto de los días de fiesta.

Tema 10: Pantalla en modo texto.

Este tema va a ser específico de Turbo Pascal para DOS. Algunas de las cosas que veremos aparecen en otras versiones de Turbo Pascal (la 3.0 para CP/M, por ejemplo), pero no todas, o en otros compiladores (como TMT Pascal Lite) y de cualquier modo, nada de esto es Pascal estándar.

Nos centraremos primero en cómo se haría con las versiones 5.0 y superiores de Turbo Pascal. Luego comentaremos cómo se haría con Turbo Pascal 3.01.

En la mayoría de los lenguajes de programación, existen "bibliotecas" (en inglés, "library") con funciones y procedimientos nuevos, que permiten ampliar el lenguaje. En Turbo Pascal, estas bibliotecas reciben el nombre de "unidades" (UNIT), y existen a partir de la versión 5.

Veremos cómo crearlas un poco más adelante, pero de momento nos va a interesar saber cómo acceder a ellas, porque Turbo Pascal incorpora unidades que aportan mayores posibilidades de manejo de la pantalla en modo texto o gráfico, de acceso a funciones del DOS, de manejo de la impresora, etc.

Así, la primera unidad que trataremos es la encargada de gestionar (entre otras cosas) la pantalla en modo texto. Esta unidad se llama CRT. Para acceder a cualquier unidad, se emplea la sentencia "uses" justo después de "program" y antes de las declaraciones de variables:

program prueba;

uses crt;

502

Page 503: Programacion en Java

var [...]

Vamos a mencionar algunos de los procedimientos y funciones más importantes. Para ver los demás, basta pasearse por la ayuda de Turbo Pascal: escribir crt y pulsar Ctrl+F1 encima de esa palabra, que nos muestra información sobre esa palabra clave (o la que nos interesase).

• ClrScr : Borra la pantalla. • GotoXY (x, y) : Coloca el cursor en unas coordenadas de la pantalla. • TextColor (Color) : Cambia el color de primer plano. • TextBackground (Color) : Cambia el color de fondo. • WhereX : Función que informa de la coordenada x actual del cursor. • WhereY : Coordenada y del cursor. • Window (x1, y1, x2, y2) : Define una ventana de texto.

Algunos "extras" no relacionados con la pantalla son:

• Delay(ms) : Espera un cierto número de milisegundos. • ReadKey : Función que devuelve el carácter que se haya pulsado. • KeyPressed : Función que devuelve TRUE si se ha pulsado alguna tecla. • Sound (Hz) : Empieza a generar un sonido de una cierta frecuencia. • NoSound: Deja de producir el sonido.

Comentarios generales:

• X es la columna, de 1 a 80. • Y es la fila, de 1 a 25. • El cursor es el cuadrado o raya parpadeante que nos indica donde seguiríamos

escribiendo. • Los colores están definidos como constantes con el nombre en inglés. Así Black = 0, de

modo que TextColor ( Black ) es lo mismo que TextColor(0). • La pantalla se puede manejar también accediendo directamente a la memoria de vídeo,

pero eso es bastante más complicado.

Aquí va un programa de ejemplo que maneja casi todo esto:

program PruebaDeCRT;

{ Acceso a pantalla en modo texto } { Exclusivo de Turbo Pascal }

{ -- Comprobado con: TP 7.0 -- }

uses crt;

503

Page 504: Programacion en Java

var bucle : byte; tecla : char;

begin ClrScr; { Borra la pantalla } TextColor( Yellow ); { Color amarillo } TextBackground( Red ); { Fondo rojo } GotoXY( 40, 13 ); { Vamos al centro de la pantalla } Write(' Hola '); { Saludamos } Delay( 1000 ); { Esperamos un segundo } Window ( 1, 15, 80, 23 ); { Ventana entre las filas 15 y 23 } TextBackground ( Blue ); { Con fondo azul } ClrScr; { La borramos para que se vea } for bucle := 1 to 100 do WriteLn( bucle ); { Escribimos del 1 al 100 } WriteLn( 'Pulse una tecla..'); tecla := ReadKey; { Esperamos que se pulse una tecla } Window( 1, 1, 80, 25 ); { Restauramos ventana original } GotoXY( 1, 24 ); { Vamos a la penúltima línea } Write( 'Ha pulsado ', tecla ); { Pos eso } Sound( 220 ); { Sonido de frecuencia 220 Hz } Delay( 500 ); { Durante medio segundo } NoSound; { Se acabó el sonido } Delay( 2000 ); { Pausa antes de acabar } TextColor( LightGray ); { Colores por defecto del DOS } TextBackground( Black ); { Y borramos la pantalla } ClrScr; end.

Finalmente, veamos los cambios para Turbo Pascal 3.01: En TP3 no existen unidades, por lo que la línea "uses crt;" no existiría. La otra diferencia es que para leer una tecla se hace con "read(kbd, tecla);" (leer de un dispositivo especial, el teclado, denotado con kbd) en vez de con "tecla := readkey". Con estos dos cambios, el programa anterior funciona perfectamente.

Para Surpas la cosa cambia un poco:

• GotoXY empieza a contar desde 0. • No existen ClrScr, TextColor ni TextBackground (entre otros), que se pueden emular como

hemos hecho en el próximo ejemplo. • No existen Window, Delay, Sound, y no son tan fáciles de crear como los anteriores.

Con estas consideraciones, el programa (que aun así se parece) queda:

program PruebaDeCRT; { Versión para SURPAS }

{ ----- Aquí empiezan las definiciones que hacen que Surpas maneje la pantalla de forma similar a Turbo Pascal ------ }

504

Page 505: Programacion en Java

{ Para comprender de donde ha salido esto, consulta el fichero IBMPC.DOC que acompaña a SURPAS }

const Black = 0; Blue = 1; Green = 2; Cyan = 3; Red = 4; Magenta = 5; Brown = 6; LightGray = 7; DarkGray = 8; LightBlue = 9; LightGreen = 10; LightCyan = 11; LightRed = 12; LightMagenta= 13; Yellow = 14; White = 15;

procedure ClrScr; begin gotoxy(0,0); write( CLREOS ); end; procedure TextColor(n: byte); begin write( chr(27), 'b', chr(n) ); end;

procedure TextBackground(n: byte); begin write( chr(27), 'c', chr(n) ); end;

{ Se podrían añadir otras posibilidades, como TextMode, HighVideo y LowVideo, etc, siguiendo este esquema, si se cree necesario }

{ ----- Final del añadido ----- }

var bucle : byte; tecla : char;

begin ClrScr; { Borra la pantalla } TextColor( Yellow ); { Color amarillo } TextBackground( Red ); { Fondo rojo } GotoXY( 30, 13 ); { Vamos al centro de la pantalla } Write(' Hola. Pulse una tecla... '); { Saludamos } read(kbd,tecla); { Esperamos que se pulse una tecla }

TextBackground ( Blue ); { Con fondo azul }

505

Page 506: Programacion en Java

ClrScr; { La borramos para que se vea } for bucle := 1 to 100 do Write( bucle, ' ' ); { Escribimos del 1 al 100 } WriteLn; { Avanzamos una línea } WriteLn( 'Pulse otra tecla..'); read(kbd,tecla); { Esperamos que se pulse una tecla } GotoXY( 0, 12 ); { Vamos al centro de la pantalla } Write( 'Ha pulsado ', tecla ); { Pos eso } TextColor( LightGray ); { Colores por defecto del DOS } TextBackground( Black ); { Y borramos la pantalla } GotoXY( 0, 23 ); { Vamos a la penúltima línea } end.

Tema 11: Manejo de ficheros.

Ahora vamos a ver cómo podemos leer ficheros y crear los nuestros propios. Voy a dividir este tema en tres partes: primero veremos como manejar los ficheros de texto, luego los ficheros "con tipo" (que usaremos para hacer una pequeña agenda), y finalmente los ficheros "generales".

Las diferencias entre las dos últimas clases de fichero pueden parecer poco claras ahora e incluso en el próximo apartado, pero en cuanto hayamos visto todos los tipos de ficheros se comprenderá bien cuando usar unos y otros.

Ficheros de texto.

Vayamos a lo que nos interesa hoy: un fichero de texto. Es un fichero formado por caracteres ASCII normales, que dan lugar a líneas de texto legible.

Si escribimos TYPE AUTOEXEC.BAT desde el DOS, veremos que se nos muestra el contenido de este fichero: una serie de líneas de texto que, al menos, se pueden leer (aunque comprender bien lo que hace cada una ya es más complicado).

En cambio, si hacemos TYPE COMMAND.COM, aparecerán caracteres raros, se oirá algún que otro pitido... es porque es un fichero ejecutable, que no contiene texto, sino una serie de instrucciones para el ordenador, que nosotros normalmente no sabremos descifrar.

Casi salta a la vista que los ficheros del primer tipo, los de texto, van a ser más fáciles de tratar que los "ficheros en general". Hasta cierto punto es así, y por eso es por lo que vamos a empezar por ellos.

Nos vamos a centrar en el manejo de ficheros con Turbo Pascal.

Para acceder a un fichero, hay que seguir unos cuantos pasos:

1. Declarar el fichero junto con las demás variables. 2. Asignarle un nombre. 3. Abrirlo, con la intención de leer, escribir o añadir. 4. Trabajar con él. 5. Cerrarlo.

La mejor forma de verlo es con un ejemplo. Vamos a imitar el funcionamiento de la orden del DOS anterior: TYPE AUTOEXEC.BAT.

506

Page 507: Programacion en Java

program MuestraAutoexec;

var fichero: text; (* Fichero de texto *) linea: string; (* Línea que leemos *)

begin assign( fichero, 'C:\AUTOEXEC.BAT' ); (* Le asignamos el nombre *) reset( fichero ); (* Lo abrimos para lectura *) while not eof( fichero ) do (* Mientras que no se acabe *) begin readln( fichero, linea ); (* Leemos una línea *) writeln( linea ); (* y la mostramos *) end; close( fichero ); (* Se acabó: lo cerramos *) end.

Eso es todo. Debería ser bastante autoexplicativo, pero aun así vamos a comentar algunas cosas:

• text es el tipo de datos que corresponde a un fichero de texto. • assign es la orden que se encarga de asignar un nombre físico al fichero que acabamos

de declarar. • reset abre un fichero para lectura. El fichero debe existir, o el programa se interrumpirá

avisando con un mensaje de error. • eof es una función booleana que devuelve TRUE (cierto) si se ha llegado ya al final del

fichero (end of file). • Se leen datos con read o readln igual que cuando se introducían por el teclado. La única

diferencia es que debemos indicar desde qué fichero se lee, como aparece en el ejemplo. • El fichero se cierra con close. No cerrar un fichero puede suponer no guardar los últimos

cambios o incluso perder la información que contiene.

Este fichero está abierto para lectura. Si queremos abrirlo para escritura, empleamos "rewrite" en vez de "reset", pero esta orden hay que utilizarla con cuidado, porque si el fichero ya existe lo machacaría, dejando el nuevo en su lugar, y perdiendo los datos anteriores. Más adelante veremos cómo comprobar si el fichero ya existe.

Para abrir el fichero para añadir texto al final, usaríamos "append" en vez de "reset". En ambos casos, los datos se escribirían con

writeln( fichero, linea );

Una limitación que puede parecer importante es eso de que el fichero debe existir, y si no el programa se interrumpe. En la práctica, esto no es tan drástico. Hay una forma de comprobarlo, que es con una de las llamadas "directivas de compilación". Obligamos al compilador a que

507

Page 508: Programacion en Java

temporalmente no compruebe las entradas y salidas, y lo hacemos nosotros mismos. Después volvemos a habilitar las comprobaciones. Ahí va un ejemplo de cómo se hace esto:

program MuestraAutoexec2;

var fichero: text; (* Fichero de texto *) linea: string; (* Línea que leemos *)

begin assign( fichero, 'C:\AUTOEXEC.BAT' ); (* Le asignamos el nombre *) {$I-} (* Deshabilita comprobación de entrada/salida *) reset( fichero ); (* Lo intentamos abrir *) {$I+} (* La habilitamos otra vez *) if ioResult = 0 then (* Si todo ha ido bien *) begin while not eof( fichero ) do (* Mientras que no se acabe *) begin readln( fichero, linea ); (* Leemos una línea *) writeln( linea ); (* y la mostramos *) end; close( fichero ); (* Se acabó: lo cerramos *) end; (* Final del "if" *) end.

De modo que {$I-} deshabilita la comprobación de las operaciones de entrada y salida, {$I+} la vuelve a habilitar, y "ioresult" devuelve un número que indica si la última operación de entrada/salida ha sido correcta (cero) o no (otro número, que indica el tipo de error).

Como último ejemplo sobre este tema, un programa que lea el AUTOEXEC.BAT y cree una copia en el directorio actual llamada AUTOEXEC.BAN. La orden del DOS equivalente sería COPY C:\AUTOEXEC.BAT AUTOEXEC.BAN

program CopiaAutoexec;

var fichero1, fichero2: text; (* Ficheros de texto *) linea: string; (* Línea actual *)

begin assign( fichero1, 'C:\AUTOEXEC.BAT' ); (* Le asignamos nombre *) assign( fichero2, 'AUTOEXEC.BAN' ); (* y al otro *) {$I-} (* Sin comprobación E/S *) reset( fichero1 ); (* Intentamos abrir uno *) {$I+} (* La habilitamos otra vez *)

508

Page 509: Programacion en Java

if ioResult = 0 then (* Si todo ha ido bien *) begin rewrite( fichero2 ); (* Abrimos el otro *) while not eof( fichero1 ) do (* Mientras que no acabe 1 *) begin readln( fichero1, linea ); (* Leemos una línea *) writeln( fichero2, linea ); (* y la escribimos *) end; writeln( 'Ya está '); (* Se acabó: avisamos, *) close( fichero1 ); (* cerramos uno *) close( fichero2 ); (* y el otro *) end (* Final del "if" *) else writeln(' No he encontrado el fichero! '); (* Si no existe *) end.

Como ejercicios propuestos:

• Un programa que vaya grabando en un fichero lo que nosotros tecleemos, como haríamos en el DOS con COPY CON FICHERO.EXT

• Un programa que copie el AUTOEXEC.BAT excluyendo aquellas líneas que empiecen por K o P (mayúsculas o minúsculas).

Ficheros con tipo.

Ya hemos visto cómo acceder a los ficheros de texto, tanto para leerlos como para escribir en ellos. Hoy nos centraremos en lo que vamos a llamar "ficheros con tipo".

Estos son ficheros en los que cada uno de los elementos que lo integran es del mismo tipo (como vimos que ocurre en un array).

En los de texto se podría considerar que estaban formados por elementos iguales, de tipo "char", pero ahora vamos a llegar más allá, porque un fichero formado por datos de tipo "record" sería lo ideal para empezar a crear nuestra propia agenda.

Pues una vez que se conocen los ficheros de texto, no hay muchas diferencias a la hora de un primer manejo: debemos declarar un fichero, asignarlo, abrirlo, trabajar con él y cerrarlo.

Pero ahora podemos hacer más cosas también. Con los de texto, el uso habitual era leer línea por línea, no carácter por carácter. Como las líneas pueden tener cualquier longitud, no podíamos empezar por leer la línea 4, por ejemplo, sin haber leído antes las tres anteriores. Esto es lo que se llama ACCESO SECUENCIAL.

Ahora sí que sabemos lo que va a ocupar cada dato, ya que todos son del mismo tipo, y podremos aprovecharlo para acceder a una determinada posición del fichero cuando nos interese, sin necesidad de pasar por todas las posiciones anteriores. Esto es el ACCESO ALEATORIO (o directo).

509

Page 510: Programacion en Java

La idea es sencilla: si cada ficha ocupa 25 bytes, y queremos leer la número 8, bastaría con "saltarnos" 25*7=175 bytes.

Pero Turbo Pascal nos lo facilita más aún, con una orden, seek, que permite saltar a una determinada posición de un fichero sin tener que calcular nada nosotros mismos. Veamos un par de ejemplos...

Primero vamos a introducir varias fichas en un fichero con tipo:

program IntroduceDatos;

type ficha = record (* Nuestras fichas *) nombre: string [80]; edad: byte end;

var fichero: file of ficha; (* Nuestro fichero *) bucle: byte; (* Para bucles, claro *) datoActual: ficha; (* La ficha actual *)

begin assign( fichero, 'basura.dat' ); (* Asignamos *) rewrite( fichero ); (* Abrimos (escritura) *) writeln(' Te iré pidiendo los datos de cuatro personas...' ); for bucle := 1 to 4 do (* Repetimos 4 veces *) begin writeln(' Introduce el nombre de la persona número ', bucle); readln( datoActual.nombre ); writeln(' Introduce la edad de la persona número ', bucle); readln( datoActual.edad ); write( fichero, datoActual ); (* Guardamos el dato *) end; close( fichero ); (* Cerramos el fichero *) end. (* Y se acabó *)

La única diferencia con lo que ya habíamos visto es que los datos son de tipo "record" y que el fichero se declara de forma distinta, con "file of TipoBase".

Entonces ahora vamos a ver cómo leeríamos sólo la tercera ficha de este fichero de datos que acabamos de crear:

program LeeUnDato;

type

510

Page 511: Programacion en Java

ficha = record nombre: string [80]; edad: byte end;

var fichero: file of ficha; bucle: byte; datoActual: ficha;

begin assign( fichero, 'basura.dat' ); reset( fichero ); (* Abrimos (lectura) *) seek( fichero, 2 ); (* <== Vamos a la ficha 3 *) read( fichero, datoActual ); (* Leemos *) writeln(' El nombre es: ', datoActual.nombre ); writeln(' La edad es: ',datoActual.edad ); close( fichero ); (* Y cerramos el fichero *) end.

El listado debe ser autoexplicativo. La única cosa que merece la pena comentar es eso del "seek( fichero, 2 )": La posición de las fichas dentro de un fichero de empieza a numerar en 0, que corresponderá a la primera posición. Así, accederemos a la segunda posición con un 1, a la tercera con un 2, y en general a la "n" con "seek(fichero,n-1)".

Y ya que como mejor se aprende es practicando, queda propuesto elaborar una agenda:

En primer lugar, va a ser una agenda que guarde una sola ficha en memoria y que vaya leyendo cada ficha que nos interese desde el disco, o escribiendo en él los nuevos datos (todo ello de forma "automática", sin que quien maneje la agenda se de cuenta). Esto hace que sea más lenta, pero no tiene más limitación de tamaño que el espacio libre en nuestro disco duro. Las posibilidades que debe tener serán:

• Mostrar la ficha actual en pantalla (automático también). • Modificar la ficha actual. • Añadir fichas nuevas. • Salir del programa.

Más adelante ya le iremos introduciendo mejoras, como buscar, ordenar, imprimir una o varias fichas, etc. El formato de cada ficha será:

• Nombre: 20 letras. • Dirección: 30 letras. • Ciudad: 15 letras. • Código Postal: 5 letras. • Teléfono: 12 letras. • Observaciones: 40 letras.

511

Page 512: Programacion en Java

Ficheros generales.

Hemos visto cómo acceder a los ficheros de texto y a los fichero "con tipo". Pero en la práctica nos encontramos con muchos ficheros que no son de texto y que tampoco tienen un tipo de datos claro.

Muchos formatos estándar como PCX, DBF o GIF están formados por una cabecera en la que se dan detalles sobre el formato de los datos, y a continuación ya se detallan los datos en sí.

Esto claramente no es un fichero de texto, y tampoco se parece mucho a lo que habíamos llamado ficheros con tipo. Quizás, un fichero "de tipo byte", pero esto resulta muy lento a la hora de leer ficheros de un cierto tamaño. Como suele ocurrir, "debería haber alguna forma mejor de hacerlo..."

La hay: declarar un fichero sin tipo, en el que nosotros mismos decidimos qué tipo de datos queremos leer en cada momento.

Ahora leeremos bloques de bytes, y los almacenaremos en un "buffer" (memoria intermedia). Para ello tenemos la orden "BlockRead", cuyo formato es:

procedure BlockRead(var F: Fichero; var Buffer; Cuantos: Word [; var Resultado: Word]);

donde

• F es un fichero sin tipo (declarado como "var fichero: file" ). • Buffer es la variable en la que queremos guardar los datos leídos. • Cuantos es el número de datos que queremos leer. • Resultado (opcional) almacena un número que indica si ha habido algún error.

Hay otra diferencia con los ficheros que hemos visto hasta ahora, y es que cuando abrimos un fichero sin tipo con "reset", debemos indicar el tamaño de cada dato (normalmente diremos que 1, y así podemos leer variables más o menos grandes indicándolo con el "cuantos" que aparece en BlockRead).

Así, abriríamos el fichero con

reset( fichero, 1 );

Los bloques que leemos con "BlockRead" deben tener un tamaño menor de 64K (el resultado de multiplicar "cuantos" por el tamaño de cada dato).

El significado de "Resultado" es el siguiente: nos indica cuantos datos ha leído realmente. De este modo, si vemos que le hemos dicho que leyera 30 fichas y sólo ha leído 15, podremos deducir que hemos llegado al final del fichero. Si no usamos "resultado" y tratamos de leer las 30 fichas, el programa se interrumpirá, dando un error.

Para escribir bloques de datos, utilizaremos "BlockWrite", que tiene el mismo formato que BlockRead, pero esta vez si "resultado" es menor de lo esperado indicará que el disco está lleno.

512

Page 513: Programacion en Java

Esta vez, es en "rewrite" (cuando abrimos el fichero para escritura) donde deberemos indicar el tamaño de los datos (normalmente 1 byte).

Como las cosas se entienden mejor practicando, ahí va un primer ejemplo, tomado de la ayuda en línea de Turbo Pascal y ligeramente retocado, que es un programa que copia un fichero leyendo bloques de 2K:

program CopiaFichero;

{ Sencillo y rápido programa de copia de ficheros, SIN comprobación de errores }

var Origen, Destino: file; CantLeida, CantEscrita: Word; NombreOrg, NombreDest: String; Buffer: array[1..2048] of Char; begin Write( 'Introduzca el nombre del fichero ORIGEN... ' ); ReadLn( NombreOrg ); Write( 'Introduzca el nombre del fichero DESTINO... ' ); ReadLn( NombreDest ); Assign( Origen, NombreOrg ); Reset( Origen, 1 ); { Tamaño = 1 } Assign( Destino, NombreDest ); Rewrite( Destino, 1 ); { Lo mismo } WriteLn( 'Copiando ', FileSize(Origen), ' bytes...' ); repeat BlockRead( Origen, Buffer, SizeOf(Buffer), CantLeida); BlockWrite( Destino, Buffer, CantLeida, CantEscrita); until (CantLeida = 0) or (CantEscrita <> CantLeida); Close( Origen ); Close( Destino ); WriteLn( 'Terminado.' ) end.

Una mejora: es habitual usar "SizeOf" para calcular el tamaño de una variable, en vez de calcularlo a mano y escribir, por ejemplo, 2048. Es más fiable y permite modificar el tipo o el tamaño de la variable en la que almacenamos los datos leídos sin que eso repercuta en el resto del programa.

Y un segundo ejemplo, que muestra parte de la información contenida en la cabecera de un fichero GIF, leyendo un "record":

program GifHeader;

Type Gif_Header = Record { Primeros 13 Bytes de un Gif }

513

Page 514: Programacion en Java

Firma, NumVer : Array[1..3] of Char; Tam_X, Tam_Y : Word; _Packed, Fondo, Aspecto : Byte; end;

Var Fich : File; Cabecera : GIF_Header; Nombre: String;

begin Write( '¿Nombre del fichero GIF (con extensión)? '); ReadLn( Nombre ); Assign( Fich, Nombre ); Reset( Fich, 1 ); { Tamaño base: 1 byte } Blockread( Fich, Cabecera, SizeOf(Cabecera) ); Close( Fich ); With Cabecera DO begin Writeln('Versión: ', Firma, NumVer); Writeln('Resolución: ', Tam_X, 'x', Tam_Y, 'x', 2 SHL (_Packed and 7)); end; end.

Ejemplo: agenda.

{ ======================================================== Ejemplo de Agenda, con lo que hemos visto hasta ahora.

Características: - Número de fichas ilimitado, pero mayor lentitud, porque los datos están permanentemente en disco, y en memoria se almacena sólo la ficha actual. - Muestra un ficha. - Se puede ver la siguiente, la anterior o la número "x" - Se pueden añadir fichas nuevas.

Posibles mejoras: - Muuuuuchas. Por ejemplo... - Mejorar la presentación. - Mejorar la entrada de datos y su modificación. - Buscar un determinado texto en las fichas. - Imprimir una o varias fichas. - Ordenar el fichero alfabéticamente. - Mantener todas o parte de las fichas en memoria para mayor velocidad. - Borrar fichas. - Clave de acceso.

514

Page 515: Programacion en Java

- Datos encriptados. - Etc, etc, etc...

Nacho Cabanes, Marzo 93. ======================================================== }

program MiniAg;

uses crt; { Manejo de la pantalla y el teclado }

const nombref: string[12]='agenda.dat'; { Nombre del fichero }

type { Nuestro tipo de datos } tipoagenda = record nombre: string[20]; direccion: string[30]; ciudad: string[15]; cp: string[5]; telef: string[12]; observ: string[40] end;

var FichAgenda: file of tipoagenda; { Fichero } ficha: TipoAgenda; { Guarda la ficha actual } NumFicha: word; { El número de ficha actual } Ultima: word; { Número de la última ficha } opcion: char; { La opción del menú que se elige }

procedure Pausa; { ----- Espera a que se pulse una tecla }var tecla: char;begin tecla := readkey;end;

procedure Saludo; { ----- Cartelito de presentación }begin TextBackground(Black); TextColor(LightGray); ClrScr; window(23,3,80,25); writeln;writeln;writeln; TextColor(LightCyan); writeln('+------------------------------+'); writeln('¦ ¦__'); writeln('¦ ¦__'); writeln('¦ A G E N D A ¦__'); writeln('¦ ¦__'); writeln('¦ ¦__'); writeln('+------------------------------+__'); writeln(' ________________________________'); TextColor(LightGray); window(1,1,80,25); gotoxy(1,25); { Para que el cursor no moleste } pausa;

515

Page 516: Programacion en Java

end;

procedure Escribe; { ----- Escribe los datos en pantalla }var i: byte; { i: para bucles }begin ClrScr; TextColor(White); TextBackground(Blue); gotoxy(1,2);write(' Agenda (ficha actual: ', NumFicha,'/',ultima,')'); clrEol; { borra hasta el final de la línea en azul } gotoxy(1,3); for i:= 1 to 80 do write('-'); { 80 guiones } TextBackground(Black); TextColor(LightGray); seek( FichAgenda, NumFicha-1 ); { se coloca en la ficha que toca } read( FichAgenda, ficha ); { y la lee } with ficha do begin { Escribe cada dato } gotoxy(1,6); writeln('Nombre: ', nombre); writeln; writeln; writeln('Dirección: ', direccion); writeln; writeln('Ciudad: ', ciudad); writeln; writeln('Código Postal: ', cp); writeln; writeln('Teléfono: ', telef); writeln; writeln; writeln('Observaciones: ',observ); end; TextColor(White); TextBackground(Blue); gotoxy(1,23); { Abajo: escribe las opciones } for i:= 1 to 80 do write('-'); gotoxy(1,24);write(' 1-Anterior 2-Posterior 3-Número' + ' 4-Añadir 5-Corregir 0-Terminar'); clrEol; TextBackground(Black); TextColor(LightGray);end;

procedure FichaNueva; { ----- Añade una ficha nueva }begin ClrScr; TextColor(Yellow); NumFicha := Ultima + 1; { Hay que escribir al final } writeln('Añadiendo la ficha ',NumFicha,'.'); TextColor(LightGray); with ficha do { Pide cada dato } begin writeln; writeln('¿ Nombre ?'); readln( nombre ); writeln; writeln('¿ Dirección ?'); readln( direccion ); writeln; writeln('¿ Ciudad ?'); readln( ciudad );

516

Page 517: Programacion en Java

writeln; writeln('¿ Código Postal ?'); readln( cp ); writeln; writeln('¿ Teléfono ?'); readln( telef ); writeln; writeln('¿ Observaciones ?'); readln( observ ); end; seek( FichAgenda, NumFicha-1 ); { Se sitúa } write( FichAgenda,ficha ); { y escribe la ficha } Ultima := Ultima + 1; { Ahora hay una más }end;

procedure Modifica; { ----- Modifica la ficha actual }var temporal:string[100]; { Almacena cada valor temporalmente }begin ClrScr; TextColor(Yellow); writeln('Corrigiendo la ficha ',NumFicha,'.'); TextColor(LightGray); with ficha do begin writeln; writeln('¿ Nombre (', nombre,') ?'); { Muestra el valor anterior } readln(temporal); { y pide el nuevo } if temporal<>'' { Si tecleamos algo, } then nombre:=temporal; { lo modifica (si no, no cambia) } writeln; writeln('¿ Dirección (',direccion,') ?'); readln(temporal); if temporal<>'' then direccion:=temporal; writeln; writeln('¿ Ciudad (',ciudad,') ?'); readln(temporal); if temporal<>'' then ciudad:=temporal; writeln; writeln('¿ Código Postal (',cp,') ?'); readln(temporal); if temporal<>'' then cp:=temporal; writeln; writeln('¿ Teléfono (',telef,') ?'); readln(temporal); if temporal<>'' then telef:=temporal; writeln; writeln('Observaciones (',observ,') ?'); readln(temporal); if temporal<>'' then observ:=temporal; end; seek( FichAgenda, NumFicha-1 ); { Como siempre... } write( FichAgenda, ficha )end;

procedure NumeroFicha; { ----- Va a la ficha con un cierto numero }var numero: word;begin ClrScr; TextColor(Yellow); writeln('Saltar a la ficha con un determinado número ');

517

Page 518: Programacion en Java

TextColor(LightGray); writeln; writeln('¿ Qué número de ficha ?'); readln( numero ); if numero>0 then { comprueba que sea válido } if numero<=ultima then NumFicha:=numero { si es <= que la última, salta } else NumFicha:=ultima; { si es mayor, se queda en la última }end;

procedure Prepara; { ----- Inicialización de las variables }begin { y apertura/creación del fichero } NumFicha := 1; Ultima := 1; assign( FichAgenda, nombref ); {$I-} { Desactiva errores de E/S } reset( FichAgenda ); { e intenta leer } {$I+} if ioresult <>0 then {Si no existen los datos} begin ClrScr; writeln(' No existen datos.', ' Pulse una tecla para introducirlos.'); pausa; rewrite( FichAgenda ); { los crea } FichaNueva; { y obliga a añadir una ficha } end; Ultima := FileSize( FichAgenda ); { Número de fichas }end;

begin { ----- Cuerpo del programa ----- } Saludo; prepara; repeat Escribe; opcion:='a'; while not (opcion in ['0'..'8']) do opcion := readkey; case opcion of '1': { Ficha anterior } if NumFicha>1 then NumFicha := NumFicha - 1; '2': { Ficha posterior } if NumFicha<ultima then NumFicha := NumFicha + 1; '3': { Número de ficha } NumeroFicha; '4': { Añadir una ficha } FichaNueva; '5': { Corregir la ficha actual } Modifica; end; until opcion='0'; { Terminar } Close( FichAgenda ); ClrScr;end.

518

Page 519: Programacion en Java

Tema 12: Creación de unidades.

Comentamos en el tema 10 que en muchos lenguajes de programación podemos manejar una serie de bibliotecas externas (en ingles, library) de funciones y procedimientos, que nos permitían ampliar el lenguaje base.

En Turbo Pascal, estas bibliotecas reciben el nombre de "unidades" (unit), y existen a partir de la versión 5.

En su momentos, empleamos la unidad CRT, que nos daba una serie de facilidades para manejar la pantalla en modo texto, el teclado y la generación de sonidos sencillos.

Iremos viendo otras unidades estándar cuando accedamos a la pantalla en modo gráfico, a los servicios del DOS, etc. Pero por ahora vamos a ver cómo podemos crear las nuestras propias.

¿Para qué? Nos podría bastar con teclear en un programa todas las funciones que nos interesen. Si creamos otro programa que las necesite, pues las copiamos también en ese y ya está...

No. Las unidades nos ayudan a conseguir dos cosas:

• La primera ventaja es que los programas sean más modulares. Que podamos dejar aparte las funciones que se encargan de batallar con el teclado, por ejemplo, y en nuestro programa principal sólo esté lo que realmente tenga este programa que lo diferencie de los otros. Esto facilita la legibilidad y con ello las posibles correcciones o ampliaciones.

• La segunda ventaja es que no tenemos distintas versiones de los mismos procedimientos o funciones. Esto ayuda a ganar espacio en el disco duro, pero eso es lo menos importante. Lo realmente interesante es que si se nos ocurre una mejora para un procedimiento, todos los programas que lo usen se van a beneficiar de él automáticamente.

Por ejemplo: imaginemos que estamos haciendo un programa de rotación de objetos en tres dimensiones. Creamos nuestra biblioteca de funciones, y la aprovechamos para todos los proyectos que vayamos a hacer en tres dimensiones. No solo evitamos reescribir en cada programa el procedimiento RotaPunto, p.ej., que ahora tomará de nuestra unidad "Graf3D" sino que si de repente descubrimos una forma más rápida de rotarlos, todos los programas que utilicen el procedimiento RotaPunto se verán beneficiados sólo con recompilarlos.

Pero vamos a lo práctico:

Una "unit" tiene dos partes: una pública, que es aquella a la que podremos acceder, y una privada, que es el desarrollo detallado de esa parte pública, y a esta parte no se puede acceder desde otros programas.

La parte pública se denota con la palabra "interface", y la privada con "implementation".

Debajo de interface basta indicar los nombres de los procedimientos que queremos "exportar", así como las variables, si nos interesase crear alguna. Debajo de implementation escribimos realmente estos procedimientos o funciones, tal como haríamos en un programa normal.

519

Page 520: Programacion en Java

Un ejemplo para que se entienda mejor...

unit miCrt1; { Unidad que "mejora" la CRT }

interface { Parte "pública", que se exporta }

procedure AtXY( X, Y: byte ; texto: string ); { Escribe un texto en ciertas coordenadas }

implementation { Parte "privada", detallada }

uses crt; { Usa a su vez la unidad CRT }

procedure AtXY( X, Y: byte ; texto: string ); begin gotoXY( X, Y); { Va a la posición adecuada } write( texto ); end;

end. { Final de la unidad }

Este ejemplo declara un procedimiento "AtXY" que hace un GotoXY y un Write en un solo paso. Un programa que lo emplease podría ser simplemente:

program PruebaDeMiCrt1;

uses miCrt1;

begin AtXY( 7, 5, 'Texto en la posición 7,5.' ); end.

520

Page 521: Programacion en Java

Este programa no necesita llamar a la unidad CRT original, sino que nuestra unidad ya lo hace por él. Vamos a mejorar ligeramente nuestra unidad, añadiéndole un procedimiento "pausa":

unit miCrt2; { Unidad que "mejora más" la CRT }

{-------------------} interface { Parte "pública", que se exporta }

procedure AtXY( X, Y: byte ; texto: string ); procedure Pausa;

{-------------------} implementation { Parte "privada", detallada }

uses crt; { Usa a su vez la unidad CRT }

var tecla: char; { variable privada: el usuario no puede utilizarla }

procedure AtXY( X, Y: byte ; texto: string ); begin gotoXY( X, Y); { Va a la posición adecuada } write( texto ); end;

procedure Pausa; { Pausa, llamando a ReadKey } begin tecla := ReadKey; { El valor de "tecla" se pierde } end;

{-------------------} end. { Final de la unidad }

521

Page 522: Programacion en Java

y un programa que usase esta unidad, junto con la CRT original podría ser:

program PruebaDeMiCrt2;

uses crt, miCrt2;

begin ClrScr; { De Crt } atXY( 7, 5, 'Texto en la posición 7,5.' ); { de miCrt2 } pausa; { de miCrt2 } end.

Finalmente, las unidades pueden contener más cosas además de funciones y procedimientos: pueden tener un "trozo de programa", su código de inicialización, como por ejemplo:

unit miCrt3; { Unidad que "mejora más" la CRT }

{-------------------} interface { Parte "pública", que se exporta }

var EraMono: boolean; { Variable pública, el usuario puede acceder a ella }

procedure AtXY( X, Y: byte ; texto: string ); procedure Pausa;

{-------------------} implementation { Parte "privada", detallada }

uses crt; { Usa a su vez la unidad CRT }

var tecla: char; { variable privada: el usuario no puede utilizarla }

procedure AtXY( X, Y: byte ; texto: string ); begin gotoXY( X, Y); { Va a la posición adecuada }

522

Page 523: Programacion en Java

write( texto ); end;

procedure Pausa; { Pausa, llamando a ReadKey } begin tecla := ReadKey; { El valor de "tecla" se pierde } end;

{-------------------} { Aquí va la inicialización } begin if lastmode = 7 { Si el modo de pantalla era monocromo } then EraMono := true { EraMono será verdadero } else EraMono := false; { si no => falso } end. { Final de la unidad }

y el programa podría usar la variable EraMono sin declararla:

program PruebaDeMiCrt3;

uses crt, miCrt3;

begin ClrScr; { De Crt } atXY( 7, 5, 'Texto en la posición 7,5.' ); { de miCrt3 } if not EraMono then atXY ( 10, 10, 'Modo de color ' ); pausa; { de miCrt3 } end.

Se podría hablar mucho más sobre las unidades, pero resumiremos:

• Al compilar una unidad se crea un fichero .TPU, al que se puede acceder desde nuestros programas con dos condiciones: que empleemos la misma versión de Turbo Pascal (el formato de las TPU varía en cada versión), y que sepamos cómo es la parte pública (interface).

• Cada unidad tiene su propio segmento de código (esto va para quien conozca la estructura de la memoria en los PC), así que una unidad pueda almacenar hasta 64k de procedimientos o funciones. Los datos son comunes a todas las unidades, con la limitación 64k en total (un segmento) para todos los datos (estáticos) de todo el programa.

523

Page 524: Programacion en Java

Si queremos almacenar datos de más de 64k en el programa, tenga una o más unidades, deberemos emplear variables dinámicas, distintas en su manejo de las que hemos visto hasta ahora (estáticas), pero eso ya lo veremos en el próximo tema...

Tema 13: Variables dinámicas.

En Pascal estándar, tal y como hemos visto hasta ahora, tenemos una serie de variables que declaramos al principio del programa o de cada módulo (función o procedimiento, unidad, etc.). Estas variables, que reciben el nombre de estáticas, tienen un tamaño asignado desde el momento en que se crea el programa.

Esto es cómodo para detectar errores y rápido si vamos a manejar estructuras de datos que no cambien, pero resulta poco eficiente si tenemos estructuras cuyo tamaño no sea siempre el mismo.

Es el caso de una agenda: tenemos una serie de fichas, e iremos añadiendo más. Si reservamos espacio para 10, no podremos llegar a añadir la número 11, estamos limitando el máximo. En este caso, la solución que vimos fue la de trabajar siempre en el disco. No tenemos límite en cuanto a número de fichas, pero es muchísimo más lento.

Lo ideal sería aprovechar mejor la memoria que tenemos en el ordenador, para guardar en ella todas las fichas o al menos todas aquellas que quepan en memoria.

Una solución "típica" es sobredimensionar: preparar una agenda contando con 1000 fichas, aunque supongamos que no vamos a pasar de 200. Esto tiene varios inconvenientes: se desperdicia memoria, obliga a conocer bien los datos con los que vamos a trabajar, sigue pudiendo verse sobrepasado, y además en Turbo Pascal tenemos muy poca memoria disponible para variables estáticas: 64K (un segmento, limitaciones heredadas del manejo de memoria en el DOS en modo real).

Por ejemplo, si en nuestra agenda guardamos lo siguientes datos de cada persona: nombre (40 letras), dirección (2 líneas de 50 letras), teléfono (10 letras), comentarios (70 letras), que tampoco es demasiada información, tenemos que cada ficha ocupa 235 bytes, luego podemos almacenar menos de 280 fichas en memoria, incluso suponiendo que las demás variables que empleemos ocupen muy poco espacio.

Todas estas limitaciones se solucionan con el uso de variables dinámicas, para las cuales se reserva espacio en el momento de ejecución del programa, sólo en la cantidad necesaria, se pueden añadir elementos después, y se puede aprovechar toda la memoria convencional (primeros 640K) de nuestro equipo.

Si además nuestro compilador genera programas en modo protegido del DOS, podremos aprovechar toda la memoria real de nuestro ordenador (4 Mb, 8 Mb, etc.). Si crea programas para sistemas operativos que utilicen memoria virtual (como OS/2 o Windows, que destinan parte del disco duro para intercambiar con zonas de la memoria principal, de modo que aparentemente tenemos más memoria disponible), podremos utilizar también esa memoria de forma transparente para nosotros.

Así que se acabó la limitación de 64K. Ahora podremos tener, por ejemplo, 30 Mb de datos en nuestro programa y con un acceso muchísimo más rápido que si teníamos las fichas en disco, como hicimos antes.

Ahora "sólo" queda ver cómo utilizar estas variables dinámicas. Esto lo vamos a ver en 3 apartados. El primero (éste) será la introducción y veremos cómo utilizar arrays con elementos que

524

Page 525: Programacion en Java

ocupen más de 64K. El segundo, manejaremos las "listas enlazadas". El tercero nos centraremos en los "árboles binarios" y comentaremos cosas sobre otras estructuras.

Introducción.

La idea de variable dinámica está muy relacionada con el concepto de puntero (pointer). Un puntero es una variable que "apunta" a una determinada posición de memoria, en la que se encuentran los datos que nos interesan.

Como un puntero almacena una dirección de memoria, sólo gastará 4 bytes de esos 64K que teníamos para datos estáticos. El resto de la memoria (lo que realmente ocupan los datos) se asigna en el momento en el que se ejecuta el programa y se toma del resto de los 640K. Así, si nos quedan 500K libres, podríamos guardar cerca de 2000 fichas en memoria, en vez de las 280 de antes. De los 64K del segmento de datos sólo estaríamos ocupando cerca de 8K (2000 fichas x 4 bytes).

Veámoslo con un ejemplo (bastante inútil, "puramente académico") que después comentaré un poco.

program Dinamicas;

type pFicha = ^Ficha; (* Puntero a la ficha *)

Ficha = record (* Datos almacenados *) nombre: string[40]; edad: byte end;

var fichero: file of ficha; (* El fichero, claro *) datoLeido: ficha; (* Una ficha que se lee *) indice: array [1..1000] of pFicha; (* Punteros a 1000 fichas *) contador: integer; (* Nº de fichas que se lee *)

begin assign( fichero, 'Datos.Dat' ); (* Asigna el fichero *) reset( fichero ); (* Lo abre *) for contador := 1 to 1000 do (* Va a leer 1000 fichas *) begin read( fichero, datoleido ); (* Lee cada una de ellas *) new( indice[contador] ); (* Le reserva espacio *) indice[contador]^ := datoLeido; (* Y lo guarda en memoria *) end; close( fichero ); (* Cierra el fichero *) writeln('El nombre de la ficha 500 es: '); writeln(indice[500]^.nombre); for contador := 1 to 1000 do (* Liberamos memoria usada *) dispose( indice[contador] ); end.

525

Page 526: Programacion en Java

El acento circunflejo (^) quiere decir "que apunta a" o "apuntado por". Así,

pFicha = ^Ficha;

indica que pFicha va a "apuntar a" datos del tipo Ficha, y

indice[500]^.nombre

será el campo nombre del dato al que apunta la dirección 500 del índice. El manejo es muy parecido al de un array que contenga records, como ya habíamos visto, con la diferencia de el carácter ^, que indica que se trata de punteros.

Antes de asignar un valor a una variable dinámica, hemos de reservarle espacio con "new", porque si no estaríamos escribiendo en una posición de memoria que el compilador no nos ha asegurado que esté vacía, y eso puede hacer que "machaquemos" otros datos, o parte del propio programa, o del sistema operativo... esto es muy peligroso, y puede provocar desde simples errores muy difíciles de localizar hasta un "cuelgue" en el ordenador o cosas más peligrosas...

Cuando terminamos de utilizar una variable dinámica, debemos liberar la memoria que habíamos reservado. Para ello empleamos la orden "dispose", que tiene una sintaxis igual que la de new:

new( variable ); { Reserva espacio } dispose( variable ); { Libera el espacio reservado }

Ya hemos visto una forma de tener arrays de más de 64K de tamaño, pero seguimos con la limitación en el número de fichas. En el próximo apartado veremos cómo evitar también esto.

Arrays de punteros.

Vimos una introducción a los punteros y comentamos cómo se manejarían combinados con arrays. Antes de pasar a estructuras más complejas, vamos a hacer un ejemplo práctico (que realmente funcione).

Tomando la base que vimos, vamos a hacer un lector de ficheros de texto. Algo parecido al README.COM que incluyen Borland y otras casas en muchos de sus programas.

Es un programa al que le decimos el nombre de un fichero de texto, lo lee y lo va mostrando por pantalla. Podremos desplazarnos hacia arriba y hacia abajo, de línea en línea o de pantalla en pantalla. Esta vez, en vez de leer un registro "record", leeremos "strings", y por comodidad los

526

Page 527: Programacion en Java

limitaremos a la anchura de la pantalla, 80 caracteres. Tendremos una capacidad, por ejemplo, de 2000 líneas, de modo que gastaremos como mucho 80*2000 = 160 K aprox.

Hay cosas que se podrían hacer mejor, pero me he centrado en procurar que sea lo más legible posible...

program Lector; { Lee ficheros de texto }

uses { Unidades externas: } crt; { Pantalla de texto y teclado }

const MaxLineas = 2000; { Para modificarlo fácilmente }

kbEsc = #27; { Código ASCII de la tecla ESC }

kbFuncion = #0; { Las teclas de función devuelven 0 + otro código } kbArr = #72; { Código de Flecha Arriba } kbPgArr = #73; { Página Arriba } kbAbj = #80; { Flecha Abajo } kbPgAbj = #81; { Página Abajo }

type LineaTxt = string [80]; { Una línea de texto } PLineaTxt = ^LineaTxt; { Puntero a lína de texto } lineas = array[1..maxLineas] { Nuestro array de líneas } of PLineaTxt;

var nomFich: string; { El nombre del fichero } fichero: text; { El fichero en sí } datos: lineas; { Los datos, claro } lineaActual: string; { Cada línea que lee del fichero } TotLineas: word; { El número total de líneas } Primera: word; { La primera línea en pantalla }

Procedure Inicio; { Abre el fichero }begin textbackground(black); { Colores de comienzo: fondo negro } textcolor(lightgray); { y texto gris } clrscr; { Borramos la pantalla } writeln('Lector de ficheros de texto.'); writeln; write('Introduzca el nombre del fichero: '); readln(nomFich);end;

Procedure Pantalla; { Pantalla del lector }begin textbackground(red); { Bordes de la pantalla } textcolor(yellow); { Amarillo sobre rojo }

527

Page 528: Programacion en Java

clrscr; { ... } gotoxy(2,1); write('Lector de ficheros de texto. Nacho Cabanes, 95.' +' Pulse ESC para salir'); gotoxy(2,25); write('Use las flechas y AvPag, RePag para moverse.'); window(1,2,80,24); { Define una ventana interior } textbackground(black); { Con distintos colores } textcolor(white); clrscr;end;

Procedure EscribeAbajo(mensaje: string); { Escribe en la línea inferior }begin window(1,1,80,25); { Restaura la ventana } textbackground(red); { Colores de los bordes: } textcolor(yellow); { Amarillo sobre rojo } gotoxy(60,25); { Se sitúa } write(mensaje); { y escribe } window(1,2,80,24); { Redefine la ventana interior } textbackground(black); { y cambia los colores } textcolor(white);end;

procedure salir; { Antes de abandonar el programa }var i: word;begin for i := 1 to TotLineas { Para cada línea leída, } do dispose(datos[i]); { libera la memoria ocupada } window(1,1,80,25); { Restablece la ventana de texto, } textbackground(black); { el color de fondo, } textcolor(white); { el de primer plano, } clrscr; { borra la pantalla } writeln('Hasta otra...'); { y se despide }end;

Procedure Pausa; { Espera a que se pulse una tecla }var tecla:char;begin tecla:=readkey;end;

Function strs(valor:word):string; { Convierte word a string }var cadena: string;begin str(valor,cadena); strs := cadena;end;

function min(a,b: word): word; { Halla el mínimo de dos números }

528

Page 529: Programacion en Java

begin if a<b then min := a else min := b;end;

procedure Lee;begin; clrscr; TotLineas := 0; { Inicializa variables } Primera := 0; while (not eof(fichero)) { Mientras quede fichero } and (TotLineas < MaxLineas) do { y espacio en el array } begin readln( fichero, LineaActual ); { Lee una línea } TotLineas := TotLineas + 1 ; { Aumenta el contador } new(datos[TotLineas]); { Reserva memoria } datos[TotLineas]^ := LineaActual; { y guarda la línea } end; if TotLineas > 0 { Si realmente se han leído líneas } then Primera := 1; { empezaremos en la primera } close(fichero); { Al final, cierra el fichero }end;

procedure Muestra; { Muestra el fichero en pantalla }var i: word; { Para bucles } tecla: char; { La tecla que se pulsa }begin; repeat for i := Primera to Primera+22 do begin gotoxy(1, i+1-Primera ); { A partir de la primera línea } if datos[i] <> nil then { Si existe dato correspondiente, } write(datos[i]^); { lo escribe } clreol; { Y borra hasta fin de línea } end; EscribeAbajo('Líneas:'+strs(Primera)+'-'+ strs(Primera+22)+'/'+strs(TotLineas)); tecla := readkey ; if tecla = kbFuncion then begin { Si es tecla de función } tecla := readkey; { Mira el segundo código } case tecla of kbArr: { Flecha arriba } if Primera>1 then Primera := Primera -1; kbAbj: { Flecha abajo } if Primera<TotLineas-22 then Primera := Primera + 1; kbPgArr: { Página arriba } if Primera>22 then Primera := Primera - 22 else Primera := 1; kbPgAbj: { Página Abajo } if Primera< (TotLineas-22) then Primera := Primera + min(22, TotLineas-23) else Primera := TotLineas-22;

529

Page 530: Programacion en Java

end; end; until tecla = kbEsc;end;

begin Inicio; { Pantalla inicial } assign(fichero, nomFich); { Asigna el fichero } {$I-} { desactiva errores de E/S } reset(fichero); { e intenta abrirlo } {$I+} { Vuelve a activar errores } if IOresult = 0 then { Si no ha habido error } begin Pantalla; { Dibuja la pantalla } Lee; { Lee el fichero } Muestra; { Y lo muestra } end else { Si hubo error } begin writeln(' ¡ No se ha podido abrir el fichero ! '); { Avisa } pausa; end; salir { En cualq. caso, sale al final }end.

Listas enlazadas.

Habíamos comentado cómo podíamos evitar las limitaciones de 64K para datos y de tener que dar un tamaño fijo a las variables del programa.

Después vimos con más detalle como podíamos hacer arrays de más de 64K. Aprovechábamos mejor la memoria y a la vez seguíamos teniendo acceso directo a cada dato. Como inconveniente: no podíamos añadir más datos que los que hubiéramos previsto al principio (2000 líneas en el caso del lector de ficheros que vimos como ejemplo).

Pues ahora vamos a ver dos tipos de estructuras totalmente dinámicas (frente a los arrays, que eran estáticos). En esta lección serán las listas, y en la próxima trataremos los árboles binarios. Hay otras muchas estructuras, pero no son difíciles de desarrollar si se entienden bien estas dos.

Ahora "el truco" consistirá en que dentro de cada dato almacenaremos todo lo que nos interesa, pero también una referencia que nos dirá dónde tenemos que ir a buscar el siguiente.

Sería algo así como:

(Posición: 1023). Nombre : 'Nacho Cabanes' DireccionFido : '2:346/3.30' SiguienteDato : 1430

530

Page 531: Programacion en Java

Este dato está almacenado en la posición de memoria número 1023. En esa posición guardamos el nombre y la dirección (o lo que nos interese) de esta persona, pero también una información extra: la siguiente ficha se encuentra en la posición 1430.

Así, es muy cómodo recorrer la lista de forma secuencial, porque en todo momento sabemos dónde está almacenado el siguiente dato. Cuando lleguemos a uno para el que no esté definido cual es el siguiente, quiere decir que se ha acabado la lista.

Hemos perdido la ventaja del acceso directo: ya no podemos saltar directamente a la ficha número 500. Pero, por contra, podemos tener tantas fichas como la memoria nos permita.

Para añadir un ficha, no tendríamos más que reservar la memoria para ella, y el Turbo Pascal nos diría "le he encontrado sitio en la posición 4079". Así que nosotros iríamos a la última ficha y le diríamos "tu siguiente dato va a estar en la posición 4079".

Esa es la idea "intuitiva". Vamos a empezar a concretar cosas en forma de programa en Pascal.

Primero cómo sería ahora cada una de nuestras fichas:

type pFicha = ^Ficha; { Puntero a la ficha }

Ficha = record { Estos son los datos que guardamos: } nombre: string[30]; { Nombre, hasta 30 letras } direccion: string[50]; { Direccion, hasta 50 } edad: byte; { Edad, un numero < 255 } siguiente: pFicha; { Y dirección de la siguiente } end;

La nomenclatura ^Ficha ya la habíamos visto. Se refiere a que eso es un "puntero al tipo Ficha". Es decir, la variable "pFicha" va a tener como valor una dirección de memoria, en la que se encuentra un dato del tipo Ficha.

La diferencia está en el campo "siguiente" de nuestro registro, que es el que indica donde se encuentra la ficha que va después de la actual.

Un puntero que "no apunta a ningún sitio" tiene el valor NIL, que nos servirá después para comprobar si se trata del final de la lista: todas las fichas "apuntarán" a la siguiente, menos la última, que "no tiene siguiente".

Entonces la primera ficha la definiríamos con

var dato1: pFicha; { Va a ser un puntero a ficha }

y la crearíamos con

531

Page 532: Programacion en Java

new (dato1); { Reservamos memoria } dato1^.nombre := 'Pepe'; { Guardamos el nombre, } dato1^.direccion := 'Su casa'; { la dirección } dato1^.edad := 45; { la edad } dato1^.siguiente := nil; { y no hay ninguna más }

Ahora podríamos añadir una ficha detrás de ella. Primero guardamos espacio para la nueva ficha, como antes:

var dato2: pFicha; { Va a ser otro puntero a ficha }

new (dato2); { Reservamos memoria } dato2^.nombre := 'Juan'; { Guardamos el nombre, } dato2^.direccion := 'No lo sé'; { la dirección } dato2^.edad := 35; { la edad } dato2^.siguiente := nil; { y no hay ninguna detrás }

y ahora enlazamos la anterior con ella:

dato1^.siguiente := dato2;

Si quisiéramos introducir los datos ordenados alfabéticamente, basta con ir comparando cada nuevo dato con los de la lista, e insertarlo donde corresponda. Por ejemplo, para insertar un nuevo dato entre los dos anteriores, haríamos:

var dato3: pFicha; { Va a ser otro puntero a ficha }

new (dato3); dato3^.nombre := 'Carlos'; dato3^.direccion := 'Por ahí'; dato3^.edad := 14; dato3^.siguiente := dato2; { enlazamos con la siguiente }

dato1^.siguiente := dato3; { y con la anterior }

532

Page 533: Programacion en Java

La estructura que hemos obtenido es la siguiente

Dato1 - Dato3 - Dato2 - nil

o gráficamente:

+------+ +------+ +------+ ¦Dato1 ¦ +->-¦Dato3 ¦ +->--¦Dato2 ¦ +------¦ ¦ +------¦ ¦ +------¦ ¦ ¦ ¦ ¦ ¦ +---------+ +--------+ +-----------+ --------- --- nil

Es decir: cada ficha está enlazada con la siguiente, salvo la última, que no está enlazada con ninguna (apunta a NIL).

Si ahora quisiéramos borrar Dato3, tendríamos que seguir dos pasos:

1.- Enlazar Dato1 con Dato2, para no perder información.

2.- Liberar la memoria ocupada por Dato3.

Esto, escrito en "pascalero" sería:

dato1^.siguiente := dato2; { Enlaza Dato1 y Dato2 } dispose(dato3); { Libera lo que ocupó Dato3 }

Hemos empleado tres variables para guardar tres datos. Si tenemos 20 datos, ¿necesitaremos 20 variables? ¿Y 3000 variables para 3000 datos?

Sería tremendamente ineficiente, y no tendría mucho sentido. Es de suponer que no sea así. En la práctica, basta con dos variables, que nos indicarán el principio de la lista y la posición actual, o incluso sólo una para el principio de la lista.

Por ejemplo, un procedimiento que muestre en pantalla toda la lista se podría hacer de forma recursiva así:

procedure MuestraLista ( inicial: pFicha ); begin

533

Page 534: Programacion en Java

if inicial <> nil then { Si realmente hay lista } begin writeln('Nombre: ', inicial^.nombre); writeln('Dirección: ', inicial^.direccion); writeln('Edad: ', inicial^.edad); MuestraLista ( inicial^.siguiente ); { Y mira el siguiente } end; end;

Lo llamaríamos con "MuestraLista(Dato1)", y a partir de ahí el propio procedimiento se encarga de ir mirando y mostrando los siguientes elementos hasta llegar a NIL, que indica el final.

Aquí va un programilla de ejemplo, que ordena los elementos que va insertando... y poco más:

program EjemploDeListas;

type puntero = ^TipoDatos; TipoDatos = record numero: integer; sig: puntero end;

function CrearLista(valor: integer): puntero; {Crea la lista, claro} var r: puntero; { Variable auxiliar } begin new(r); { Reserva memoria } r^.numero := valor; { Guarda el valor } r^.sig := nil; { No hay siguiente } CrearLista := r { Crea el puntero } end;

procedure MuestraLista ( lista: puntero ); begin if lista <> nil then { Si realmente hay lista } begin writeln(lista^.numero); { Escribe el valor } MuestraLista (lista^.sig ) { Y mira el siguiente } end; end;

procedure InsertaLista( var lista: puntero; valor: integer); var

534

Page 535: Programacion en Java

r: puntero; { Variable auxiliar } begin if lista <> nil then { Si hay lista } if lista^.numero<valor { y todavía no es su sitio } then { hace una llamada recursiva: } InsertaLista(lista^.sig,valor) { mira la siguiente posición } else { Caso contrario: si hay lista } begin { pero hay que insertar ya: } new(r); { Reserva espacio, } r^.numero := valor; { guarda el dato } r^.sig := lista; { pone la lista a continuac. } lista := r { Y hace que comience en } end { el nuevo dato: r } else { Si no hay lista } begin { deberá crearla } new(r); { reserva espacio } r^.numero := valor; { guarda el dato } r^.sig := nil; { no hay nada detrás y } lista := r { hace que la lista comience } end { en el dato: r } end;

var l: puntero; { Variables globales: la lista }

begin l := CrearLista(5); { Crea una lista e introduce un 5 } InsertaLista(l, 3); { Inserta un 3 } InsertaLista(l, 2); { Inserta un 2 } InsertaLista(l, 6); { Inserta un 6 } MuestraLista(l) { Muestra la lista resultante } end.

Ejercicios propuestos:

1- ¿Se podría quitar de alguna forma el segundo "else" de InsertaLista?

2- ¿Cómo sería un procedimiento que borrase toda la lista?

3- ¿Y uno de búsqueda, que devolviera la posición en la que está un dato, o NIL si el dato no existe?

4- ¿Cómo se haría una lista "doblemente enlazada", que se pueda recorrer hacia adelante y hacia atrás?

Árboles binarios.

535

Page 536: Programacion en Java

Hemos visto cómo crear listas dinámicas enlazadas, y cómo podíamos ir insertando los elementos en ellas de forma que siempre estuviesen ordenadas.

Hay varios casos particulares. Sólo comentaré un poco algunos de ellos:

• Una pila es un caso particular de lista, en la que los elementos siempre se introducen y se sacan por el mismo extremo (se apilan o se desapilan). Es como una pila de libros, en la que para coger el tercero deberemos apartar los dos primeros (salvo los malabaristas). Este tipo de estructura se llama LIFO (Last In, First Out: el último en entrar es el primero en salir).

• Una cola es otro caso particular, en el que los elementos se introducen por un extremo y se sacan por el otro. Es como se supone que debería ser la cola del cine: los que llegan, se ponen al final, y se atiende primero a los que están al principio. Esta es una estructura FIFO (First In, First Out).

Estas dos son estructuras más sencillas de programar de lo que sería una lista en su caso general, pero que son también útiles en muchos casos.

Finalmente, antes de pasar con los "árboles", comentaré una mejora a estas listas enlazadas que hemos visto. Tal y como las hemos tratado, tienen la ventaja de que no hay limitaciones tan rígidas en cuanto a tamaño como en las variables estáticas, ni hay por qué saber el número de elementos desde el principio. Pero siempre hay que recorrerlas desde DELANTE hacia ATRÁS, lo que puede resultar lento. Una mejora relativamente evidente es lo que se llama una lista doble o lista doblemente enlazada: si guardamos punteros al dato anterior y al siguiente, en vez de sólo al siguiente, podremos avanzar y retroceder con comodidad. Pero tampoco profundizaremos más en ellas.

ARBOLES.

En primer lugar, veamos de donde viene el nombre. En las listas, después de cada elemento venía otro (o ninguno, si habíamos llegado al final). Pero también nos puede interesar tener varias posibilidades después de cada elemento, 3 por ejemplo. De cada uno de estos 3 saldrían otros 3, y así sucesivamente. Obtendríamos algo que recuerda a un árbol: un tronco del que nacen 3 ramas, que a su veces se subdividen en otras 3 de menor tamaño, y así sucesivamente hasta llegar a las hojas.

Pues eso será un árbol: una estructura dinámica en la que cada nodo (elemento) puede tener más de un "siguiente". Nos centraremos en los árboles binarios, en los que cada nodo puede tener un hijo izquierdo, un hijo derecho, ambos o ninguno (dos hijos como máximo).

Para puntualizar a un más, aviso que trataremos los árboles binarios de búsqueda, en los que tenemos prefijado un cierto orden, que nos ayudará a encontrar un cierto dato dentro de un árbol con mucha rapidez.

¿Y como es este "orden prefijado"? Sencillo: para cada nodo tendremos que:

• la rama de la izquierda contendrá elementos menores. • la rama de la derecha contendrá elementos mayores.

Como ejemplo, vamos a introducir en un árbol binario de búsqueda los datos 5,3,7,2,4,8,9

Primer número: 5 (directo)

536

Page 537: Programacion en Java

5

Segundo número: 3 (menor que 5)

5 / 3

Tercer número: 7 (mayor que 5)

5 / \ 3 7

Cuarto: 2 (menor que 5, menor que 3)

5 / \ 3 7 / 2

Quinto: 4 (menor que 5, mayor que 3)

5 / \ 3 7 / \ 2 4

Sexto: 8 (mayor que 5, mayor que 7)

5 / \ 3 7 / \ \ 2 4 8

Séptimo: 9 (mayor que 5, mayor que 7, mayor que 8)

5 / \

537

Page 538: Programacion en Java

3 7 / \ \ 2 4 8 \ 9

¿Y qué ventajas tiene esto? Pues la rapidez: tenemos 7 elementos, lo que en una lista supone que si buscamos un dato que casualmente está al final, haremos 7 comparaciones; en este árbol, tenemos 4 alturas => 4 comparaciones como máximo.

Y si además hubiéramos "equilibrado" el árbol (irlo recolocando, de modo que siempre tenga la menor altura posible), serían 3 alturas.

Esto es lo que se hace en la práctica cuando en el árbol se va a hacer muchas más lecturas que escrituras: se reordena internamente después de añadir cada nuevo dato, de modo que la altura sea mínima en cada caso.

De este modo, el número máximo de comparaciones que tendríamos que hacer sería log2(n), lo que supone que si tenemos 1000 datos, en una lista podríamos llegar a tener que hacer 1000 comparaciones, y en un árbol binario, log2(1000) => 10 comparaciones como máximo. La ganancia es evidente.

No vamos a ver cómo se hace eso de los "equilibrados", que sería propio de un curso de programación más avanzado, o incluso de uno de "Tipos Abstractos de Datos" o de "Algorítmica", y vamos a empezar a ver rutinas para manejar estos árboles binarios de búsqueda.

Recordemos que la idea importante es todo dato menor estará a la izquierda del nodo que miramos, y los datos mayores estarán a su derecha.

Ahora la estructura de cada nodo (dato) será:

type

TipoDato = string[10]; { Vamos a guardar texto, por ejemplo }

Puntero = ^TipoBase; { El puntero al tipo base } TipoBase = record { El tipo base en sí: } dato: TipoDato; { - un dato } hijoIzq: Puntero; { - puntero a su hijo izquierdo } hijoDer: Puntero; { - puntero a su hijo derecho } end;

Y las rutinas de inserción, búsqueda, escritura, borrado, etc., podrán ser recursivas. Como primer ejemplo, la de escritura de todo el árbol (la más sencilla) sería:

538

Page 539: Programacion en Java

procedure Escribir(punt: puntero); begin if punt <> nil then { Si no hemos llegado a una hoja } begin Escribir(punt^.hijoIzq); { Mira la izqda recursivamente } write(punt^.dato); { Escribe el dato del nodo } Escribir(punt^.hijoDer); { Y luego mira por la derecha } end; end;

Si alguien no se cree que funciona, que coja lápiz y papel y lo compruebe con el árbol que hemos puesto antes como ejemplo. Es muy importante que este procedimiento quede claro antes de seguir leyendo, porque los demás serán muy parecidos.

La rutina de inserción sería:

procedure Insertar(var punt: puntero; valor: TipoDato); begin if punt = nil then { Si hemos llegado a una hoja } begin new(punt); { Reservamos memoria } punt^.dato := valor; { Guardamos el dato } punt^.hijoIzq := nil; { No tiene hijo izquierdo } punt^.hijoDer := nil; { Ni derecho } end else { Si no es hoja } if punt^.dato > valor { Y encuentra un dato mayor } Insertar(punt^.hijoIzq, valor) { Mira por la izquierda } else { En caso contrario (menor) } Insertar(punt^.hijoDer, valor) { Mira por la derecha } end;

Y finalmente, la de borrado de todo el árbol, casi igual que la de escritura:

procedure BorrarArbol(punt: puntero); begin if punt <> nil then { Si queda algo que borrar } begin BorrarArbol(punt^.hijoIzq); { Borra la izqda recursivamente } dispose(punt); { Libera lo que ocupaba el nodo } BorrarArbol(punt^.hijoDer); { Y luego va por la derecha } end; end;

539

Page 540: Programacion en Java

Un comentario: esta última rutina es peligrosa, porque indicamos que "punt" está libre y después miramos cual es su hijo izquierdo (después de haber borrado la variable). Esto funciona en Turbo Pascal , porque marca esa zona de memoria como disponible pero no la borra físicamente, pero puede dar problemas con otros compiladores o si se adapta esta rutina a otros lenguajes (como C). Una forma más segura de hacer lo anterior sería:

procedure BorrarArbol2(punt: puntero); var derecha: puntero; { Aquí guardaremos el hijo derecho } begin if punt <> nil then { Si queda algo que borrar } begin BorrarArbol2(punt^.hijoIzq); { Borra la izqda recursivamente } derecha := punt^.hijoDer; { Guardamos el hijo derecho <=== } dispose(punt); { Libera lo que ocupaba el nodo } BorrarArbol2(derecha); { Y luego va hacia por la derecha } end; end;

O bien, simplemente, se pueden borrar recursivamente los dos hijos antes que el padre (ahora ya no hace falta ir "en orden", porque no estamos leyendo, sino borrando todo):

procedure BorrarArbol(punt: puntero); begin if punt <> nil then { Si queda algo que borrar } begin BorrarArbol(punt^.hijoIzq); { Borra la izqda recursivamente } BorrarArbol(punt^.hijoDer); { Y luego va hacia la derecha } dispose(punt); { Libera lo que ocupaba el nodo } end; end;

Ejercicios propuestos:

• Implementar una pila de strings[20]. • Implementar una cola de enteros. • Implementar una lista doblemente enlazada que almacene los datos leídos de un fichero

de texto (mejorando el lector de ficheros que vimos). • Hacer lo mismo con una lista simple, pero cuyos elementos sean otras listas de caracteres,

en vez de strings de tamaño fijo. • Añadir la función "buscar" a nuestro árbol binario, que diga si un dato que nos interesa

pertenece o no al árbol (TRUE cuando sí pertenece; FALSE cuando no). • ¿Cómo se borraría un único elemento del árbol?

Apéndice 1: Otras órdenes no vistas.

540

Page 541: Programacion en Java

A lo largo del curso ha habido órdenes que no hemos tratado, bien porque no encajasen claramente en ningún tema, o bien porque eran demasiado avanzadas para explicarlas junto con las más parecidas.

En primer lugar vamos a ir viendo las que podían haber formado parte de temas anteriores, y después las que faltan. Tampoco pretendo que esto sea una recopilación exhaustiva, sino simplemente mencionar algunas órdenes interesantes que parecían haberse quedado en el tintero.

Los temas que se van a comentar son:

• Bucles: break, continue. • Saltos: goto, label. • Punteros: getmem, freemem, pointer. • Fin del programa: exit, halt. • Números aleatorios: rnd, randomize. • Funciones: abs, sin, cos, arctan, round, trunc, sqr, sqrt, exp, ln, odd, potencias.

Bucles: break, continue.

En Turbo Pascal 7.0, tenemos dos órdenes extra para el control de bucles, tanto si se trata de "for", como de "repeat" o "until". Estas órdenes son:

• Break: sale del bucle inmediatamente. • Continue: pasa a la siguiente iteración.

Un ejemplo "poco útil" que use ambas podría ser escribir los números pares hasta el 10 con un for:

for i := 1 to 1000 do { Nos podemos pasar } begin if i>10 then break; { Si es así, sale } if i mod 2 = 1 then continue; { Si es impar, no lo escribe } writeln( i ); { Si no, lo escribe } end;

Esto se puede imitar en versiones anteriores de TP con el temible "goto", que vamos a comentar a continuación:

Goto.

Es una orden que se puede emplear para realizar saltos incondicionales. Su empleo está bastante desaconsejado, pero en ciertas ocasiones, y se lleva cuidado, puede ser útil (para salir de bucles fuertemente anidados, por ejemplo, especialmente si no se dispone de las dos órdenes anteriores.

El formato es

goto etiqueta

541

Page 542: Programacion en Java

y las etiquetas se deben declarar al principio del programa, igual que las variables. Para ello se usa la palabra "label". Vamos a verlo directamente con un ejemplo:

label uno, dos;

var donde: byte;

begin writeln('¿Quiere saltar a la opción 1 o a la 2?'); readln (donde); case donde of 1: goto uno; 2: goto dos; else writeln('Número incorrecto'); end; exit;

uno: writeln('Esta es la opción 1. Vamos a seguir...'); dos: writeln('Esta es la opción 2.'); end.

Punteros: getmem, freemem, pointer.

Habíamos hablado de "new" y "delete", en las que el compilador decide la cantidad de memoria que debe reservar. Pero también podemos obligarle a reservar la que nosotros queramos, lo que nos puede interesar, por ejemplo, para leer cadenas de caracteres de longitud variable.

Para esto usamos "getmem" y "freemem", en los que debemos indicar cuánta memoria queremos reservar para el dato en cuestión. Si queremos utilizarlos como new y dispose, reservando toda la memoria que ocupa el dato (será lo habitual), podemos usar "sizeof" para que el compilador lo calcule por nosotros:

type TipoDato = record Nombre: string[40]; Edad: Byte; end;

var p: pointer;

begin if MaxAvail < SizeOf(TipoDato) then

542

Page 543: Programacion en Java

Writeln('No hay memoria suficiente') else begin GetMem(p, SizeOf(TipoDato)); { Trabajaríamos con el dato, y después... } FreeMem(p, SizeOf(TipoDato)); end; end.

Por cierto, "pointer" es un puntero genérico, que no está asociado a ningún tipo concreto de dato. No lo vimos en la lección sobre punteros, porque para nosotros lo habitual es trabajar con punteros que están relacionados con un cierto tipo de datos.

Exit, halt.

En el tema 8 (y en los ejemplos del tema 6) vimos que podíamos usar exit para salir de un programa.

Esto no es exacto del todo: exit sale del bloque en el que nos encontremos, que puede ser un procedimiento o una función. Por tanto, sólo sale del programa si ejecutamos exit desde el cuerpo del programa principal.

Si estamos dentro de un procedimiento, y queremos abandonar el programa por completo, deberíamos hacer más de un "exit". Pero también hay otra opción: la orden "halt".

Halt sí que abandona el programa por completo, estemos donde estemos. Como parámetro se le puede pasar el "Errorlevel" que se quiere devolver al DOS, y que se puede leer desde un fichero batch.

Por ejemplo: "halt" o "halt(0)" indicaría una salida normal del programa (sin errores), "halt(1)" podría indicar que no se han encontrado los datos necesarios, etc.

Números aleatorios.

Si queremos utilizar números aleatorios en nuestros programas, podemos emplear la función "random". Si la usamos tal cual, nos devuelve un número real entre 0 y 1. Si usamos el formato "random(n)", lo que devuelve es un número entero entre 0 y n-1.

Para que el ordenador comience a generar la secuencia de números aleatorios, podemos usar "randomize", que toma como semilla un valor basado en el reloj, lo que supone que sea suficientemente aleatorio:

var i:integer;

begin Randomize; for i := 1 to 50 do Write (Random(1000), ' '); end.

543

Page 544: Programacion en Java

Funciones.

La mayoría de las que vamos a ver son funciones matemáticas que están ya predefinidas en Pascal. Muchas de ellas son muy evidentes, pero precisamente por eso no podíamos dejarlas sin mencionar al menos:

• Abs: valor absoluto de un número. • Sin: seno de un cierto ángulo dado en radianes. • Cos: coseno, análogo. • ArcTan: arco tangente. No existe función para la tangente, que podemos calcular como

sin(x)/cos(x). • Round: redondea un número real al entero más cercano. • Trunc: trunca los decimales de un número real para convertirlo en entero. • Int: igual que trunc, pero el resultado sigue siendo un número real. • Sqr: eleva un número al cuadrado. • Sqrt: halla la raíz cuadrada de un número. • Exp: exponencial en base e, es decir, eleva un número a e. • Ln: calcula el logaritmo neperiano (base e) de un número. • Odd: devuelve TRUE si un número es impar. • Potencias: no hay forma directa de elevar un número cualquiera a otro en pascal, pero

podemos imitarlo con "exp" y "ln", así:

function elevado(a,b: real): real; begin elevado := exp(b *ln(a) ); end;

begin writeln(elevado(2,3)); end.

La deducción de esta fórmula es fácil, conociendo las propiedades de los logaritmos y las exponenciales.

a^b = exp ( ln ( a^b )) = exp ( b * ln ( a ))

Apéndice 2: Palabras reservadas de Turbo Pascal (ordenadas alfabéticamente):

Son las palabras clave que no se pueden redefinir:

and: Para encadenar comparaciones ("y"), u operador de bits (producto).

array: Matriz o vector a partir de un tipo base.

asm: Accede al ensamblador integrado.

begin: Comienzo de una sentencia compuesta.

case: Elección múltiple según los valores de una variable.

544

Page 545: Programacion en Java

const: Comienzo de la declaración de constantes.

constructor: Inicializa un objeto que contiene métodos virtuales.

destructor: Elimina un objeto que contiene métodos virtuales.

div: División entera.

do: Ordenes a realizar en un bucle "for" o en un "while".

downto: Indica el final de un bucle "for" descendente.

else: Acción a realizar si no se cumple la condición dada en un "if".

end: Fin de una sentencia compuesta.

file: Tipo de datos: fichero.

for: Bucle repetitivo. Se usa con "to" o "downto", y con "do".

function: Definición de una función.

goto: Salta a una cierta etiqueta dentro del programa.

if: Comprobación de una condición. Se usa con "then" y "else".

implementation: Parte privada de una unidad: detalle que los procedimientos y funciones que la forman, junto con los datos no accesibles.

in: Comprueba la pertenencia de un elemento en un conjunto.

inherited: Accede al método de igual nombre del objeto padre.

inline: Inserta instrucciones en código máquina dentro del programa.

interface: Parte pública de una unidad: datos accesibles y cabeceras de los procedimientos y funciones.

label: Etiqueta a la que se puede saltar con "goto".

mod: Resto de la división entera.

nil: Puntero nulo.

not: Operador lógico ("no") y de bits.

object: Declaración de un objeto.

of: Indica el tipo base de un "array", "string", "set" o "file".

or: Operador lógico ("ó") y de bits (suma).

545

Page 546: Programacion en Java

packed: Tipo de array. No necesario en Turbo Pascal.

procedure: Definición de un procedimiento.

program: Nombre de un programa.

record: Tipo de datos: registro.

repeat: Repite una serie de órdenes hasta ("until") que se cumple una condición.

set: Tipo conjunto.

shl: Desplazamiento de bits hacia la izquierda.

shr: Desplazamiento de bits hacia la derecha.

string: Tipo de datos: cadena de caracteres.

then: Acción a realizar si se cumple una condición dada por "if".

to: Indica el final de un bucle "for" ascendente.

type: Comienzo de la declaración de tipos.

unit: Comienzo (y nombre) de la definición de una unidad.

until: Condición de fin de un "repeat".

uses: Lista las unidades que va a utilizar un programa u otra unidad.

var: Comienzo de la declaración de variables.

while: Repite una orden mientras se dé una condición.

with: Para acceder a los campos de un "record" sin tener que nombrarlo siempre.

xor: Operación de bits (suma exclusiva).

546

Page 547: Programacion en Java

1- Introducción

1.1- PERL

1.1.1- Que es PERL

Perl (Practical Extraction and Report Languaje) es un lenguaje de programación surgido a inicios de los noventas, que busca antes que nada el facilitar la elaboración de tareas comunes en sistemas tipo UNIX, donde tradicionalmente las tareas de administración y proceso de datos se realiza con herramientas muy rudimentarias y por demás hostiles al usuario o administrador. Pero que se aplican sobre grandes cantidades de información (por lo regular texto) por lo que se requiere que sean de alto rendimiento.

Su autor, Larry Wall ([email protected]) realizó Perl casi como una obra altruista, de modo que hasta PERL 5.X su distribución es gratuita, sin que por eso tenga menos poder o consistencia.

1.1.2- Para que sirve

Perl surgió como una opción para una gran cantidad de herramientas de UNIX en las cuales basa su propia sintaxis, buscando el mínimo sacrificio de su desempeño por una máxima facilidad de programación e integración, sigue la filosofía de mantener un ambiente que sea capaz de detectar y corregir pequeñas omisiones del programador, y de proporcionarle una forma abreviada de realizar múltiples tareas. En una palabra, es una utilería que pretende facilitar el proceso de grandes volúmenes de información sin sacrificar el rendimiento.

1.1.3- Donde Puede Usarse

Las plataformas donde Perl se ha desarrollado mas son los servidores UNIX, por sus necesidades de administración y lo robusto de su manejo de memoria y de procesos (requisitos de PERL hacia el S.O.) además de la facilidad de Perl para realizar los así llamados CGIs, interfaces para comunicar recursos del servidor con un servicio de internet particular (como podría ser WWW o gopher), En otras plataformas, PC en particular, se han desarrollado versiones que mantienen un razonable grado de funcionalidad, pero en realidad, el sistema DOS no tiene un manejo lo bastante bueno de los procesos o de la memoria para permitir a PERL dar un buen desempeño, además de que no es común ver en PC necesidades de administración de la magnitud de un servidor institucional. Sin embargo, puede practicarse la programación en PERL de PC, o incluso elaborar programas de reporteo en el , sin embargo, es algo que no se ha popularizado hasta hoy.

1.1.4- Que fuentes de información existen

Los libros que son ya clásicos sobre Perl son los libros del camello y la llama de la editorial Nutshell, además, existen magníficas introducciones y manuales de referencia que se pueden obtener vía internet. Aun cuando es imposible mencionar con precisión las fuentes de información de un medio tan dinámico con algo tan estático como este documento. Debe notarse, además que estas referencias están en inglés.

Para buscar información, Yahoo! por supuesto:

http://www.yahoo.com/Computers_and_Internet/Languajes/Perl/

Curso de Perl

547

Page 548: Programacion en Java

Fuente de información general y referencia de documentos:

http://pubweb.nexor.co.uk/public/perl/perl.html

http://www.khoros.unm.edu:80/staff/neilb/perl/perl5.html

Una buena formada introducción:

http://www.khoros.unm.edu:80/staff/neilb/perl/introduction/home.html

Documentación:

http://www-cgi.cs.cmu.edu/cgi-bin/perl-man

http://www.perl.com/perl/info/documentation.html

ftp://ftp.cdrom.com/pub/perl/CPAN/doc/manual/html/index.html

ftp://ftp.uoknor.edu/mirrors/CPAN/doc/manual/html/index.html

Debo recalcar que por la misma naturaleza de Perl, los recursos disponibles y las herramientas que se pueden utilizar cambian muy a menudo, por lo que es indispensable dedicar algún esfuerzo a mantenerse al día para evitar un desperdicio mayor de esfuerzo por no utilizar los nuevos recursos disponibles.

1.2- Filosofía de Perl

"Hay mas de una forma de hacerlo"

-Larry Wall, autor del lenguaje de programación Perl.

Perl no establece ninguna filosofía de programación (de hecho, no se puede decir que sea orientado a objetos, modular o estructurado aun cuando soporta directamente todos estos paradigmas), los objetivos que se tuvieron en cuenta al diseñar la sintaxis de Perl fueron la facilidad de aprendizaje y de uso y la claridad de código, las cuales, considero que son necesarias (aunque pueden escribirse programas en Perl complejos e inteligibles si así se desea).

Por si fuese poco, Perl no es ni un compilador ni un interprete, esta en un punto intermedio, cuando mandamos a ejecutar un programa en Perl, se compila el código fuente a un código intermedio en memoria, se le optimiza (como si fuésemos a elaborar un programa ejecutable) pero es ejecutado por un motor, como si se tratase de un interprete. El resultado final, es que utilizamos algo que se comporta como un interprete pero que tiene un rendimiento comparativo al de programas compilados. Sin embargo, ya existen compiladores de Perl con la versión 5.

En fin, Perl no nos forza a nada, pero como es lógico hay ciertas reglas que recomiendo seguir para facilitar nuestro trabajo:

• Claridad. En la mecánica de programación actual, los programas deben de ser entendibles por la persona que nos suceda en tareas de mantenimiento, de lo contrario perjudicamos tanto a nuestros compañeros de trabajo como a nuestra propia libertad para progresar y mantenernos libres de preocupaciones.

548

Page 549: Programacion en Java

• Indentación. Una costumbre ya clásica de la programación, en lo personal, y a lo largo de los ejemplos de este documento, indento el código dos espacios hacia adelante al abrir cada bloque, y termino la indentación al terminar el bloque, de modo que las llaves de apertura y cierre quedan a la vista y en la misma columna, solas en sus renglones (esto incrementa algo el numero de líneas, pero facilita sobremanera la búsqueda y corrección de los diversos bloques de control).

• Nombres de variables y demás. En lo personal, procuro dar la máxima claridad a los nombres de las variables sin hacerlos demasiado grandes, el nombre de los contadores y variables que guardan valores concernientes a un pequeño segmento de código por lo regular son de un par de letras (c1, c2, ... cx para los contadores, s1, s2, etc para cadenas de entrada etc.) mientras que las variables que afectan a diversos segmentos (a modo de regla, que tienen su definición en una pantalla distinta a donde se usan) tienen nombres explicativos que procuro no excedan los 12 caracteres. Además, los nombres de archivos se usan con mayúsculas (ARCHENT, ARCHSAL, etc) y las clases tienen su primera letra mayúscula.

• Comentarios. Para facilitar la comprensión de un programa no hay como explicarlo, y los comentarios son el medio ideal para hacerlo, hay por lo menos tres comentarios que considero que siempre deben incluirse en un programa: Que hace el programa, Quien lo escribió y Cuando inicio y termino de escribirlo, sobretodo en el contexto de una organización, estos tres simples comentarios pueden hacer la diferencia entre desechar un programa como indescifrable o dedicarle algún tiempo para revisarlo. Además, considero prudente comentar dentro del código la forma en que el programa deberá ejecutarse, parámetros, y su sintaxis, así como comentar las estructuras de control como un modo de explicar la funcionalidad al detalle y recalcar con comentarios las funciones que cumplen las variables.

• Sencillez. Es cómodo en ocasiones el comprimir una serie de instrucciones en una sola línea, queda al criterio decidir cuando se gana en claridad con un código mas o menos extenso, pero no debe titubearse en comentar el código que sea "comprimido".

1.3- Diferencias entre Perl 4.3 y 5.X, Como elegir versión

Actualmente existen dos versiones altamente populares de Perl, la 4.3 y la 5.0X, de hecho hay diferencias importantes entre una versión y otra. Seguramente el lector se pregunta porque surge la duda entre usar una versión vieja y una nueva, por regla general las nuevas versiones son mejores que las anteriores de modo que las opacan en todo sentido, Perl no es la excepción a esta regla, el único factor que impide una transición inmediata es que no son 100% compatibles. La versión 5 de Perl es una reescritura total que ya incluye un manejo de estructuras abstractas de datos mucho mas poderoso, incluso, soporta la orientación a objetos a su manera (tema que no trato en esta introducción). De modo que las librerías, por ejemplo para creación de CGIs no funcionan de una función a otra por lo que la migración es poco practica.

Así pues, la decisión sobre que versión utilizar depende del trabajo que haya sido realizado con anterioridad, si ya se tiene un sistema completo o grande en Perl 4, es recomendable mantenerlo en Perl 4 por el resto de su vida útil, pero para desarrollos nuevos es por mucho, mas recomendable iniciarlos con Perl 5 o en su caso, la versión mas reciente que este disponible, por experiencia se que la capacitación para adaptarse a la nueva versión es extraordinariamente corta dada la importancia de las mejoras. Una tercera opción (que es por mucho la mas recomendable si se tienen los recursos) consiste en instalar las dos versiones de modo que convivan en el sistema.

2- Programación básica

549

Page 550: Programacion en Java

En este capitulo daremos una rápida revisión a los conceptos mas usuales que se encuentran en un programa en Perl, trataremos de ver la implementación para ambas versiones 4 y 5 cuando sea posible, especificando siempre en que versión esta el ejemplo original (dando preferencia a la versión 5) y al menos las alternativas para implementarlo en la otra versión.

Los ejemplo requerirán para funcionar, que se tenga Perl instalado en la maquina en que se practique y deberá conocerse la ruta completa al binario de Perl.

Mas que sentirse en libertad de experimentar con los diversos ejemplos, deben sentirse obligados a experimentar, modificar y explorar por su cuenta cada que sea posible, tomando los ejemplo solo como un punto seguro de partida. Para este fin, recomiendo que se tenga acceso desde el inicio a alguna referencia del lenguaje (ya sea el libro Programming Perl (del Camello) de la Nutshell o alguna referencia disponible por WWW), este documento pretende explicar lo básico de un modo accesible para que después pueda el nuevo programador de Perl abordar sin temor los materiales mas detallados.

2.1- Estructura Básica de un programa, programa Holamundo.

Como se menciona en la introducción, Perl no obliga a casi nada, así pues, lo que planteo como estructura básica es mas bien una convención que un requisito del lenguaje, a diferencia de Pascal (por ejemplo) Perl no tiene una plantilla para sus programas y si se adoptan algunos protocolos es solo por comodidad.

Los programas de Perl, por lo regular, inician con la línea:

#!/usr/bin/perl

Esta línea, indica al SO que lo que sigue es un script de Perl, y que "Perl" (el programa con el cual debe ejecutarse) esta en el directorio "/usr/bin", la secuencia "#!" es una secuencia que UNIX reconoce, no Perl.

Un método alterno, que funciona para otras plataformas, es: en lugar de colocar esa primera línea ejecutamos:

Perl nombre_del_script.pl

de modo que directamente se ejecuta el interprete de Perl pasándole como primer parámetro el script a ejecutar (los demás parámetros se tomaran como parámetros al programa). Si se requiere deberá sustituirse "Perl" por la ruta completa del programa y el nombre que el programa tenga.

Para los ejemplos sucesivos, tomare la suposición de que se trabaja en un sistema UNIX con el interprete de Perl en el directorio "/usr/bin".

Y eso es toda la estructura básica!.

Programa Hola mundo:

#!/usr/bin/perlprint "Hola Mundo\n"; #Saluda#Programa Hola Mundo, Daniel Sol Llaven, 1996, como parte del tutorial de Perl

550

Page 551: Programacion en Java

Este programa, se escribe como un archivo de texto común, (al que recomiendo se le ponga extensión ".pl") y se cambian sus permisos para que pueda ser ejecutado (por lo regular con un "chmod 700 nombre_programa.pl" en sistemas UNIX), para ejecutarlo simplemente se invoca el nuevo script "nombre_programa.pl", hay que recordar que para el sistema particular en que se este trabajando es muy probable que deba modificarse "/usr/bin/" por otra ruta.

Así, la ejecución en un sistema UNIX podría verse como:

>chmod 700 Hola.pl>Hola.plHola Mundo>Hola.plHola Mundo>

Ejecutando dos veces el script pretendo mostrar que no es necesario cambiar el modo de ejecución del script sino solo una vez.

Expliquemos las tres líneas que constituyen nuestro primer script:

#!/usr/bin/perl

Esta línea, propiamente dicho, no es parte del script de Perl, sino una instrucción al SO para indicar que las siguientes líneas deberán interpretarse por el programa "/usr/bin/Perl", si se omite, o no funciona en el sistema operativo, podemos ejecutar el programa de la siguiente forma:

>/usr/bin/Perl Hola.plHola Mundo>print "Hola Mundo\n"; #Saluda

Esta es la única instrucción del programa y esta acompañada por un comentario, la instrucción es:

print "Hola Mundo\n";

que pudo ser escrita como:

print("Hola Mundo\n");

sin que esto implicara el mínimo cambio en funcionalidad.

a continuación tiene el comentario:

#Saluda

La sintaxis de C para comentarios no funciona en Perl, pues se usa a "/" para expresiones regulares.

#Programa Hola Mundo, Daniel Sol Llaven, 1996, como parte del tutorial de Perl

Esta línea es otro comentario, el SO y Perl consideran todo lo que este en una línea que inicie con "#" como comentario. (excepto en el caso de que sea una primera línea, el SO lo interpreta como la indicación del interprete, pero Perl lo ignora como a cualquier otro comentario).

551

Page 552: Programacion en Java

2.2- Estructuras de Datos Básicas

En nuestro programa Hola.pl no utilizamos ninguna variable, por lo que para explicar estos conceptos necesitaremos un nuevo programa, HolaP.pl que es una versión personalizada de Hola.pl.

#!/usr/bin/perl#Programa Hola Mundo personalizado, Daniel Sol Llaven 1996, Tutorial Perl.print "Hola ?como te llamas?:";$nombre=<STDIN>;chop($nombre);print "$nombre!, bonito nombre, cuantos años tienes?:";$edad=<STDIN>;print "sabias que te faltan ".(100-$edad)." para tener 100?\nAdios!\n";

Su ejecución genera resultados similares a los siguientes:

>HolaP.plHola ?como te llamas?:DanielDaniel!, bonito nombre, cuantos años tienes?:22sabias que te faltan 78 para tener 100?Adiós!>

En este programa, aparecen muchos elementos novedosos que iremos revisando poco a poco a lo largo de este documento, por el momento, revisemos que hace línea por línea:

#!/usr/bin/perl#Programa Hola Mundo personalizado, Daniel Sol Llaven 1996, Tutorial Perl.

Cabecera normal del programa, incluido comentario indispensable.

print "Hola ?como te llamas?:";

Esta línea escribe "Hola ?como te llamas?:" pero nótese que no escribe una vuelta de carro al final (omite el "\n").

$nombre=<STDIN>;

Aquí asignamos a $nombre un renglón escrito por la entrada estándar del programa.

Perl define al "archivo" STDIN como su entrada estándar, y el operador <> que indica la lectura de una línea de un archivo de texto, de modo que <STDIN> indica la lectura de un renglón de la entrada estándar (al menos para el ejemplo!), este tema se tratara con cuidado en la sección 2.5 y 3.3 de este documento que tratan de operaciones con archivos.

Para fines prácticos, usaremos <STDIN> como la expresión que lee una línea de entrada del usuario desde teclado.

chop($nombre);

552

Page 553: Programacion en Java

Perl, al leer la línea escrita por el usuario, también toma el enter que el usuario da, de modo que en el ejemplo, la entrada que di en vez de ser "Daniel" se puede escribir como "Daniel\n", (siendo "\n" el carácter de vuelta de carro), la función "chop()" tiene la función de eliminar el ultimo carácter a nuestra cadena, de modo que después de esta instrucción $nombre solo tiene "Daniel". Por las características de la lectura de renglones de archivos, chop() es un elemento casi constante en programas de Perl.

(Pruébalo!, revisa que sucede si comentas esta línea, veras que al imprimir el nombre imprime también el retorno de carro avanzando una línea hacia abajo).

print "$nombre!, bonito nombre, cuantos años tienes?:";

Esta línea nos muestra como se imprime el contenido de nuestras variables de forma sencilla; notamos que en la cadena a imprimir se encuentra $nombre, el compilador lo interpreta y reemplaza por su valor, (para imprimir "$" se pone "\$"), el resto de la cadena es completamente convencional.

$edad=<STDIN>;

Esta línea, lee otra línea de TEXTO y la coloca en la variable $edad (con todo y retorno de carro).

print "sabias que te faltan ".(100-$edad)." para tener 100?\nAdios!\n";

Esta línea imprime un mensaje que esta dividido en tres partes, la primera y la ultima son cadenas convencionales, que están concatenadas (operador .) con una operación aritmética. Hay que notar que edad era un renglón de la entrada estándar, y no un numero, sin embargo no hay conflicto de tipo al operarlo con un entero, ¿porque? porque los dos, para Perl, son del mismo tipo, son escalares, después discutiremos que son los escalares y que mecanismo de conversión se utiliza para extraer el entero del texto contenido en $edad.

(Experimenta!, prueba que pasa si los valores que se dan a edad no son enteros, que pasa si son reales o cadenas!)

Después de nuestro primer encuentro con las variables de Perl, el sentimiento mas probable es el de perplejidad, ¿como maneja sus variables?, ¿a que me refería conque enteros, reales y cadenas son del mismo tipo?, y si es así, ¿como serán los demás tipos?.

En realidad, las variables de Perl pretenden simplificarnos la vida, pero debemos comprender como pretenden facilitar las cosas para no terminar peleando contra ellas.

2.2.1- Clases de Datos

Perl reconoce tres clasificaciones básicas de datos, y dos especiales, por claridad, llamaremos clases a estas diversas clasificaciones, y tipos a las formas usuales de datos.

Las diversas clases se distinguen entre si por el símbolo que antecede al nombre de la variable (por ejemplo $nombre es una variable de tipo escalar que se llama "nombre"), debe notarse que no hay relación entre variables del mismo nombre si son de clases distintas.

A continuación, pongo una tabla de las clases de datos y los tipos que contienen:

Clase Símbolo Tipos

553

Page 554: Programacion en Java

Escalar $ Entero, Real, Cadena, Referencia*

Arreglo @ Arreglo de escalares

Hash % Arreglo Asociativo de escalares

Archivo (ninguno) Identificador de Archivo

Type Glob * Cualquiera**

* Las referencias son exclusivas de Perl 5, son el equivalente a apuntadores.

** Las variables tipo Glob se usaban como sustituto de las referencias en Perl 4, son obsoletas para Perl 5 y en lo personal no recomiendo su uso sino en casos muy particulares.

2.2.1.1- Escalares

Nota: Algunas características de las conversiones entre tipos son exclusivas de Perl 5, pero la sintaxis y características generales son las mismas para ambas versiones. Por lo que en general, el tratamiento expuesto es valido para las dos.

El escalar, es la clase de datos que engloba los tipos convencionales de datos, de modo que podemos decir:

$v1="Daniel Sol Llaven";$v1=100;$v1=89.12;

Sin que esto implique ningún cambio en la naturaleza de $v1, en todo momento es un escalar.

Aun cuando la compatibilidad de datos enteros y reales es fácil de imaginar, la conversión implícita en el caso de las cadenas no lo es, sin embargo la regla es bastante simple.

por ejemplo:

$v1="123y321";

crea un escalar $v1, que contiene la cadena "123y312", ¿pero que pasa si le deseamos sumar 4?. Perl, interpreta $v1 como entero (o punto flotante) para esto, toma todos los caracteres del inicio que forman un numero correcto y ese numero es el que interpreta; de modo que:

print $v1+1;

imprimirá:

124

Del mismo modo, como ejemplo:

$v2="123.123.123"+1

554

Page 555: Programacion en Java

da el valor 124.123 a la variable $v1 (punto flotante).

Otro punto importante de las variables en general es que, aunque en ningún momento se declaran como de un tipo o de otro, si se pueden "destruir", o revisar que hayan recibido algún valor en la ejecución del programa, esto se logra mediante las funciones defined() y undef(). defined nos indica si la variable que le pasamos como argumento ha sido definida (es decir, existe en el programa) o no. undef toma una variable y la "elimina" de modo que ya no tiene valor y defined la reporta como no utilizada.

Los valores lógicos de verdadero y falso se manejan de modo similar al de C, cualquier valor escalar no nulo o cero se considera verdadero. Debe tenerse cuidado con las cadenas "0" pues si se evalúan como número resultaran en falso aun cuando no son cadenas nulas.

Los escalares son los constituyentes de las demás estructuras de datos, por lo que al explicar los arreglos, hashes, referencias y archivos haremos muchas referencias a ellos.

2.2.1.2- Arreglos

Los arreglos en Perl son simplemente, arreglos dinámicos de escalares, es decir, se pueden usar cualesquiera elementos en el arreglo y Perl se encargará de hacer al arreglo de la dimensión adecuada.

La definición de un arreglo con valores constantes es:

@a1=("hola",123,43.2,"adios");

Esta definición, crea el arreglo @a1 con cuatro elementos, dos cadenas, un entero y un real, en realidad, cuatro escalares, para hacer referencia a los elementos escalares de un arreglo se usan los corchetes [] indicando el índice del elemento (de cero al numero de elementos menos uno) de modo que:

print "$a1[0]\n$a1[1]\n$a1[2]\n$a3[3]\n";

resulta para el arreglo anterior:

hola12343.2adiós

Nótese que el elemento de un arreglo ya no es un arreglo, sino un escalar, y debe especificarse como tal, con $ en vez de @. No es lo mismo el escalar a1 que un elemento del arreglo a1.

$a1 Es un elemento distinto que $a1[]

Por claridad, recomiendo que no se dupliquen nombres de arreglos (o cualquier otra cosa) con escalares, aunque Perl da un manejo independiente a las diversas entidades, de modo que si se hace no habrá quejas de parte de Perl.

Es importante señalar que los arreglos y hashes no pueden ser elementos de un arreglo, si se intenta esto, los arreglos o hashes serán "aplanados" a elementos que se agregan al arreglo. Por ejemplo:

555

Page 556: Programacion en Java

(a,b,(c,d),e)==(a,b,c,d,e)

Es decir, el arreglo constante a,b, arregló con c,d , e es equivalente al arreglo que contiene a,b,c,d,e. Pues al formar el primer arreglo, el sub-arreglo c,d es "aplanado" a elementos que se agregan al arreglo principal. Algo similar sucede a los hashes.

Para hacer arreglos de arreglos o arreglos como elementos de hashes, se utilizan las referencias.

2.2.1.3- Hash o Arreglos Asociativos

El Hash, o arreglo asociativo es una ampliación del arreglo, en la que en vez de ubicar los elementos por posición se les ubica por una "llave" es pues un arreglo de parejas ordenadas que se accesa por el primer elemento, el símbolo de los hashes es %, y la forma de declarar un hash con constantes es muy similar a la forma para declarar un arreglo:

%h1=("ll1",1,"ll2",2,"ll3",3);

Esta declaración crea un hash con tres parejas ordenadas, las llaves son "ll1", "ll2", "ll3", para usarlo se pueden usar expresiones del tipo:

$ndx="ll3";print "$h1{ll1}\n$h1{"ll2"}\n$h1{$ndx}\n";

resultando:

123

Al igual que en los arreglos, cada elemento de un hash es un escalar, de modo que debe anteponerse $ en vez de % (pues no estamos haciendo referencia a todo el hash, sino a un elemento escalar del hash), pero a diferencia del los arreglos, en vez de usar [] para indicar el índice se usan las llaves {}.

Dentro de las llaves, en el ejemplo, usamos las tres formas principales de dar el índice:

ll1 - En esta forma Perl adivina correctamente que ll1 es una cadena y que esta es la llave.

"ll2" - En esta forma decimos explícitamente el valor de la llave deseada.

$ndx- como $ndx="ll3" Perl puede determinar el valor de la llave correcta.

Para índices, al igual que para hashes, también puede usarse el valor de variables o de expresiones para especificar el índice.

2.2.1.4- Equivalencias de Clases

Como ya revisamos, al hacer referencia a un elemento particular de un arreglo o hash obtenemos un escalar (en ves de todo el arreglo o hash). Este tipo de "cambios" de clase son el propósito de esta sección, pues pueden resultar algo confusos aunque, una vez comprendidos, dan una buena parte del sabor peculiar de Perl.

556

Page 557: Programacion en Java

Básicamente, Perl distingue dos tipos de contextos para evaluar una expresión: como escalar y como arreglo. El primero se refiere a las expresiones que han de regresar un escalar (del tipo que sea) como resultado, y el segundo a aquellas expresiones que han de regresar un arreglo o conjunto de escalares como resultado. Muchas expresiones pueden ser evaluadas en ambos contextos y obtener, según el contexto, un resultado distinto, esto lo revisaremos con cuidado conforme vayamos revisando estas expresiones.

Revisemos los cambios que ocurren a los datos de una cierta clase cuando los evaluamos en otro contexto:

Contexto escalar arreglo

Clase escalar arreglo hash

escalar escalar El valor Se convierte en Vacío, no definido original el único elemento del arreglo

arreglo arreglo Numero de El arreglo de Los elementos pares elementos valores (0,2,4,...) forman (usualmente) originales las llaves y los nones los datos del nuevo hash.

hash cociente que arreglo con las El hash original representa la parejas eficiencia del llave1,valor1,lla hash ve2,valor2,...

Las transiciones mas utilizadas son las de arreglo a escalar, las de arreglo a hash y de hash a arreglo, la primera porque permite conocer el tamaño de los arreglos y las segundas porque proveen los mecanismos para inicializar los hashes con arreglos constantes y para "aplanarlos" con fines de almacenamiento e impresión.

A menudo, se representa un arreglo con una sola cadena que contiene separadores para los diversos elementos, Perl implementa la función "split" que divide estas cadenas en arreglos, su sintaxis básica es:

split($delim,$cadena)

donde

$delim es la expresión que representa los delimitadores de elementos y

$cadena es la cadena a dividir.

Como ya se mencionó, generará un arreglo de cadenas donde los elementos son las subcadenas de $cadena que estén separadas por cadenas $delim.

por ejemplo, una cadena como:

557

Page 558: Programacion en Java

$registro="Daniel:22:daniel@simba";@reg=split(":",$registro);print "Nombre:$reg[0]\nEdad:$reg[1]\nEmail:$reg[2]\n";

genera un resultado similar a:

Nombre:DanielEdad:22Email:daniel@simba

Cuidado! no todas las funciones de Perl convierten de igual forma los arreglos en escalares, por lo que debe provarse o investigar primero que efectivamente en el contexto en que hagamos la conversión el resultado sea el deseado.

Ejemplos:

Arreglo constante

@arreglo=(1,2,"hola",3,"adios");

inicializa el arreglo @arreglo con los elementos 1,2,"hola",3,"adios", (todos escalares). la notación de los elementos entre paréntesis define un arreglo constante, el equivalente a un numero o cadena constante cuyo valor asignáramos a una variable pero en el contexto de los arreglos.

"Hash" constante

%hash=("llave1","dato1","llave2","dato2); #arreglo constante a hash

Inicializa el %hash con las llaves "llave1" y "llave2" poniéndole como contenidos "dato1" y "dato2" respectivamente. De hecho, no especificamos un "hash constante" como en el caso del arreglo, sino que especificamos un arreglo constante el cual pasa por una transformación de clase para asignarse a un hash, de modo que la expresión es equivalente a:

@arreglo=("llave1","dato1","llave2","dato2); #arreglo constante a arreglo%hash=@arreglo;#arreglo a hash

Cardinalidad de un arreglo

@arreglo=(1,2,3,4);$elementos=@arreglo;

En este caso $elemento recibe el valor 4, pues son 4 los elementos del arreglo, nótese que el índice máximo en el arreglo es de solo tres, pues el primer elemento es el elemento 0 y Perl 4 regresa, con esta misma expresión el índice máximo en lugar del número de elementos como Perl 5.

Asignación a arreglo constante

($a,$b,$c)=(1,2,3);

Esta expresión es equivalente a:

558

Page 559: Programacion en Java

$a=1;$b=2;$c=3;

Pero debe tenerse cuidado, el siguiente código:

($a,$b,$c)=(1,2,3);@a=($a,$b,$c);$a=7;$b=8;$c=9;

Da al arreglo @a el valor del arreglo constante (1,2,3), y no, como podría pensarse. (7,8,9). Cuando se genera un arreglo constante se evalúan los elementos que lo forman como constantes escalares y se le asigna después, para obtener resultados diferentes se deberá usar un arreglo de referencias.

Arreglos con arreglos

@a=(1,2,3);@b=(5,6,7);@c=(@a,4,@b,8);

Estas expresiones generan tres arreglos, (1,2,3), (5,6,7) y (1,2,3,4,5,6,7,8), y no, como podría pensarse un arreglo de arreglos, cuando incluimos un arreglo dentro de otro, Perl "aplana" el arreglo insertado como si insertara uno a uno todos sus elementos en la posición indicada del arreglo que ha de contenerlos, para hacer arreglos de arreglos deberemos usar referencias a estos.

2.2.3- Tipos Especiales de Datos

Llamo a estos "tipos especiales" porque nos permiten hacer cosas inposibles con otros tipos, por ejemplo, el evitar ser "aplanado" en los arreglos. Además, incluyo en esta sección a los Archivos, pues aunque su sintaxis se parece a las de las variables, su funcionalidad es bien distinta. Sin embargo, por tratarse de solo una introducción omito discutir sobre los "type globs" que son la aproximación a las referencias que perl 4 implementa.

2.2.3.1- Referencias

Nota: Este tipo de datos es exclusivo de Perl 5, Perl 4 usaba los type globs para realizar algunas de estas funciones, pero el proceso es mucho mas complicado y obscuro.

Las referencias son el equivalente lógico de los apuntadores, son un tipo de dato que no contiene información en si misma, sino que permite manejar indirectamente los datos contenidos en otra entidad.

Las referencias, sin embargo, no forman una clase nueva de datos, sino que son tratados como un tipo mas de escalar.

La definición de referencias se realiza por el operador referencia "\" (backslash) , funciona de modo similar a "&" en C, y aunque a diferencia de C no hay un operador de derreferencia la sintaxis para acceder al dato original es muy sencilla.

559

Page 560: Programacion en Java

A diferencia de C, los objetos que creemos mediante referencias no quedan como cadáveres en memoria, Perl lleva un registro de las diversas referencias a cada elemento en memoria y automáticamente lo destruye cuando descubre que nadie hace ya referencia a él, de modo que pueden usarse las referencias sin miedo, aunque con la precaución necesaria para no perder los datos que almacenamos en ellas.

2.2.3.1.1- Creación

Podemos crear referencias de varias formas, las que considero mas sencillas y útiles son:

Usando el operador de referenciación en una variable, o valor, en el caso de una variable, es crear un medio alterno de acceder al valor de la variable.

$rescalar=\$escalar;$rarreglo=\@arreglo;$rhash=\%hash;$rrutina=\&subrutina; #la programación de subrutinas la revisaremos mas adelante.$globref=\*ARCHIVO; #solo revisaremos los type globs para usarlos como referencias a archivos.

Creando objetos "anónimos" que solo pueden ser accesados por medio de la referencia.

Escalares

$rescalar=\"hola"; #referencia a la cadena anónima "hola"

Arreglos:

$rarreglo=[1,2,3]; # referencia al arreglo anónimo (1,2,3)

Hashes

$rhash={"llave1" => "dato1","llave2" => "dato2"};

Nótese que en esta representación usamos además el operador "=>" que indica una pareja de llave y dato, las que se separan por comas; esta notación también es valida para hashes convencionales, pero la claridad que agrega es mejor utilizada al declarar hashes anónimos.

Como elementos de arreglos y/o hashes para formar estructuras de datos complejas, al ser escalares, son elementos del arreglo sin mas complicación, de modo que los arreglos a los que hacen referencia se mantienen intáctos.

Por ejemplo, para formar un hash que contenga arreglos de nombres de letras:

%rhletras={"latinas" => ["a","b","c"],"gregas" => ["alfa","beta","gamma"]};

Esta sentencia forma un hash donde las llaves son cadenas y los datos son referencia a arreglos.

Pueden convinarse las referencias a arreglos y a hashes anónimos a voluntad para formar una estructura de datos tan compleja como se desee.

560

Page 561: Programacion en Java

2.2.3.1.2- Uso

De nada sirven las referencias si no podemos extraer y modificar los datos contenidos en los elementos señalados por la referencia, en Perl la forma para obtener el valor y para modificarlo es casi la misma, solo las abreviaturas de las referencias para obtener el valor funcionaran de modo distinto cuando tratemos de asignarles valor.

Aun cuando Perl tiene varias formas de "derreferenciar" la información, solo discutiré dos por considerarlas las mas sencillas, sin embargo, recomiendo una revisión al capitulo PERLREF de la referencia de Perl 5.X para una explicación mas detallada y a consciencia del manejo de las referencias.

Uso "simbólico" de las referencias.

Por regla general, podemos usar las referencias como si se sustituyeran antes de ejecutar el código (aunque en realidad, no sea así), de modo que el código:

$nombre="entero";$entero=5;$rentero=\$entero;$$nombre=6;$$rentero=7;

efectivamente cambia el valor de $entero de 5 a 6 y despues de 6 a 7, del mismo modo podemos usar las referencias refiriéndonos a cualquier otro tipo.

por ejemplo, para arreglos:

$rarreglo=[1,2,3,4] #crea arreglo anónimo (1,2,3,4)$$rarreglo[2]="tres"; #modifica el arreglo anónimo a (1,2,"tres",4)@$rarreglo=(); #limpia el arreglo anónimo

Uso con el operador de derreferencia.

Como una forma de abreviar el proceso para referencias a arreglos y hashes, se añadió el operador -> que indica que el escalar es una referencia a hash o arreglo y espera el índice después.

por ejemplo:

$rarreglo->[5]="algo";

coloca "algo" como el 6o elemento del arreglo al que hace referencia $rarreglo.

$rhash->{"alfa"}="uno";

coloca la pareja ("alfa" => "uno") en el hash al que hace referencia $rhash.

Uso abreviado del operador de derreferencia.

Se pueden obtener referencias a referencias, y se pueden hacer arreglos de referencias, de modo que los arreglos multidimencionales se pueden elaborar con la misma mecánica que en C.

561

Page 562: Programacion en Java

$ra3d->[0]->[0]->[0]=1; #pone un "0" en la primera celda de un arreglo tridimencional

Nótese que al crear perl los arreglos de las dimensiones adecuadas automáticamente no hay problemas de invasión de memoria no reservada (aunque un uso descuidado puede ocupar cantidades demasiado grandes de memoria). Y esta aplicación se considero lo bastante frecuente para implementar una abreviatura, de modo que la expresión anterior es equivalente a:

$ra3d[0][0][0]=1;

Lo cual le da mas claridad de lectura al código (pero debe tenerse cuidado de no confundirlo con un elemento de un arreglo, $ra3d es una referencia a un arreglo de referencias y no un arreglo normal. Por claridad podríamos usar:

$ra3d->[0][0][0]=1;

Ejemplo: A continuación, coloco el listado de un programa que usa las estructuras de datos que mencionamos anteriormente usando su potencia lo mas posible sin que pierdan claridad, recomiendo que no solo se pruebe el programa, sino que se elabore una versión en la que se exploren los puntos que particularmente les resulten mas interesantes.

2.2.3.2- Archivos

Uno de los usos mas probados y comunes de Perl es el procesamiento de archivos de texto para generar otros archivos (por lo regular de texto) que implican cierto proceso de los datos leídos originalmente. Además, Perl no conoce mas entrada que la proveniente de archivos (asociando la entrada, salida y salida de errores a archivos "de ambiente") por lo que en cierta medida ya hemos revisado el uso básico de archivos, solo que ahora le daremos la explicación correspondiente a las entidades que vimos antes.

Como en cualquier otro lenguaje, el ciclo de uso normal de un archivo es:

Apertura Mediante "open" inicializa una variable de archivo

Uso Lectura secuencial por líneas

Cerrado Mediante close

Perl tiene implementaciónes para manejar archivos binarios y archivos aleatorios, pero el uso de archivos de texto secuenciales es lo mas común en UNIX, por lo que considero a este manejo como básico y le explico aquí.

Básicamente, se puede decir que hay tres tipos de archivos que se manejan de modo muy similar:

• Archivos comunes • Programas de los que capturamos la entrada o la salida • La entrada, salida o salida estándar de errores.

Estos tres tipos de archivos se manejan igual (con la limitación de que algunos solo reciben lectura o escritura) y el momento en que se determina el tipo verdadero del archivo, es en el momento de

562

Page 563: Programacion en Java

la apertura, por lo que ha dedicado una sección exclusivamente a la apertura mientras que al uso y cierre los he concentrado en la sección que siguiente a esta.

2.2.3.2.1- Apertura

La entrada, salida y salida de errores estándar, están abiertas por default, pero en cualquier otro sentido se utilizan igual que cualquier otro archivo, debe tenerse cuidado al planear los entubamientos de información para evitar que nuestro programa espere por siempre una entrada que no le ha de llegar, sin embargo no es necesario ser paranoico al respecto. Si tratamos de usarlas después de cerrarlas el resultado de las lecturas será nulo y los intentos de salida no tendrán efecto.

Los archivos convencionales se abren con la instrucción "open", la cual es, por decir poco, muy interesante, en esta sección solo revisaremos su uso básico.

open VARCHIVO,EXPRESION

VARCHIVO es la variable mediante la que usaremos el archivo, no tiene indicador de clase y por convención la manejamos en mayúsculas.

EXPRESIÓN es, digamos, el nombre y modo en que habrá de abrirse el archivo.

Los archivos, se darán con los nombres nativos del S.O., y los modos se especifican con las siguientes cadenas al inicio del nombre (que imitan al shell).

<archivo abre "archivo" para lectura

>archivo abre "archivo" para escritura, borrándolo si existe.

>>archivo abre "archivo" para escritura, agregando la nueva información al final

+<archivo abre "archivo" para lectura y escritura

+>archivo abre "archivo" para lectura y escritura borrando "archivo" al abrirlo.

|programa ejecuta "programa" y reasigna su entrada estándar a lo que escribamos en el

programa| ejecuta "programa" y reasigna su salida estándar para lectura de nuestro programa

Debe notarse que a diferencia de los archivos no se puede ejecutar programas para lectura Y escritura.

Es importante notar que esta forma de ejecutar programas desde perl es muy poderosa, pero no es la única, por lo regular se usa "system" para ejecutar programas si la salida no nos importa o encerrando con las comillas ` al comando si la salida es sencilla (un renglón) lo que evalúa al comando como si fuese una expresión resultando en la salida del programa (las comillas ' funcionan de distinta forma entre Perl 4 y 5 al ejecutar el programa).

563

Page 564: Programacion en Java

También se pueden abrir la salida y entrada estándar mediante open, pero estas funciones así como la de cerrarlas puede tener consecuencias en la ejecución del programa que son algo complicadas de explicar, por lo que recomiendo que de desear manejar estas operaciones se use la referencia de Perl.

2.2.3.2.2- Uso y Cerrado

Básicamente, se puede usar un archivo de dos formas, por renglones, (terminados por el carácter de vuelta de carro) o por carácter (byte). Revisare solo la primera opción, porque el manejo por carácter tiene implicaciones sobre la naturaleza del sistema de explicación muy larga y porque son mas usuales los archivos de texto en las aplicaciones de Perl.

El operador que nos permite leer de un archivo es "<>" teniendo la variable de archivo dentro de el, así por ejemplo:

$reng=<STDIN>;

Que, como ya hemos mencionado; lee un renglón de la entrada estándar y lo coloca en la variable $reng, incluida la vuelta de carro que termine el renglón.

Si el renglón termina con carácter de fin de archivo, este no se incluye en el renglón, y lecturas sucesivas de STDIN esperaran indefinidamente por una nueva entrada (así que debe tenerse cuidado al usar la entrada estándar o confiar en el usuario).

STDIN es la variable de archivo que identifica la entrada estándar, así como

STDOUT es la variable que identifica la salida estándar y

STDERR es la que identifica la salida estándar de errores.

@contenidos=<STDIN>;

Esta vez, estamos evaluando al operador <> en un contexto de arreglo, y su comportamiento varia, como arreglo <> regresa un arreglo de cadenas, donde cada cadena es un renglón del archivo, de modo que la expresión anterior leerá todo lo que se introduzca en la entrada estándar y lo coloca en el arreglo @contenido. Esta es una forma mas segura de usar la entrada estándar, pues ahora cada elemento del arreglo equivale a una lectura sucesiva de STDIN en contexto escalar.

Si sustituimos STDIN por cualquier otra variable de archivo (o valor de variable de referencia a una variable de archivo) estaremos cargando el archivo ya sea renglón por renglón o todo completo a variables de memoria.

Como caso especial, si evaluamos la expresión:

<ARCHIVO>;

cargaremos un renglón del archivo al que ARCHIVO hace referencia y lo coloca en la variable $_ de la cual hablaremos mas adelante.

A continuación, esta el listado de un programa sencillo realizado en perl que demuestra la potencialidad de las estructuras de datos y lo básico del manejo de archivos, este programa esta hecho para perl 4, pero no creo que tenga problemas para correr bajo perl 5, sin embargo, debe

564

Page 565: Programacion en Java

recordarse que la ruta hacia perl muy probablemente deberá cambiarse o deberá ejecutarse perl dándole el programa como parámetro.

#!/usr/bin/perl# Programa hecho en perl4 que crea un archivo de parejas de nombre y # dirección IP tomando las direcciones de un archivo de la forma:##131.178.80.32#206.103.64.98#ppp16-07.rns.tamu.edu#130.178.80.20## Etc, entiendase que los "#" no están en el archivo de entrada que # tiene el nombre "salida" y que esta en el directorio activo.open(DIR,"<salida"); # Abre el archivo de entradawhile($dir=<DIR>) # Lee todas sus líneas una a una{ chop $dir; # les corta el fin de línea open(NS,"nslookup $dir|"); # Invoca nslookup y recibe el resultado undef(@inf,$nombre,$inf); # Destruye datos viejos while($reng=<NS>) # revisa el resultado del nslookup { chop $reng; # quita fin de línea $reng=~s/ //g; # quita espacios @inf=split(":",$reng); # Hace un arreglo con los resultados

# que vienen como "Name: servidor.unam.mx" # "Address: 132.248.100.100" por ejemplo. if($inf[0] eq "Name") {$nombre=$inf[1]}; #Toma el nombre del servidor if($inf[0] eq "Address") {$dir=$inf[1]}; #Toma su dirección IP } close NS; # Cierra los resultados y termina la ejecución de nslookup if(defined($nombre)) # Si asigno valor a $nombre... { $hres{$dir}=$nombre; # Llena un Hash de la dirección y el nombre print "%\n"; # Indica el éxito de la resolución } else { print "#\n"; #Indica el fracaso de la resolución }}close(DIR); #Cierra su archivo de entradaopen(DIRS,">direcciones"); # Abre archivo de salida de resultadosprint "\nimprimiendo resutlados...\n";foreach $dir (keys %hres) # Para todas las llaves de %hres{ print DIRS "$dir $hres{$dir}\n"; #imprime la pareja llave-valor}close(DIRS); # cierra su archivo de salida.

2.3- Operaciones Básicas

565

Page 566: Programacion en Java

Como se habrá podido distinguir en los ejemplos vistos hasta ahora, Perl cuenta con una amplia y muy interesante gama de operadores, en esta sección daremos una rápida revisión a los mas importantes, confiando en que el uso de la mayoría de los operadores será casi intuitivo.

Para obtener mayor información sobre los operadores, la sección PERLOP de la referencia es la mejor fuente de información detallada.

Las variables, comillas, paréntesis y funciones con paréntesis tienen la precedencia mas alta en perl, de hecho, Perl los interpreta mas como operadores unarios que se comportan como funciones por la agrupación de los parámetros sobre los que actúan. Después de estos, la precedencia de los operadores mas usuales es:

Mas alta:

Posición Operador

no asocia ++ y --

derecha ! ~ \ y la versión unaria de + y -

izquierda =~ !~

izquierda * / % x

izquierda + - .

no asocia < > <= >= lt gt le ge

no asocia == != <=> eq ne cmp

izquierda &

izquierda | ^

izquierda &&

izquierda ||

no asocia ..

derecha ?:

derecha = += -= *= etc.

izquierda , =>

izquierda not

izquierda and

izquierda or xor

Mas baja.

566

Page 567: Programacion en Java

2.3.1- Operaciones Aritméticas

Estas operaciones interpretaran al escalar como un numero (ya sea entero o real) y operaran aritméticamente sobre el, toda cadena que no inicie con una cifra será interpretada como un cero por estos operadores.

En orden de Precedencia tenemos:

++ y -- Son los operadores de auto incremento y auto decremento tan conocidos para los programadores de C. Su uso se puede resumir en la siguiente tabla:

Posición ++ --

Antepuesto Incrementa 1 antes de evaluar Decrementa 1 Antes de evaluar

Pospuesto Incrementa 1 después de Decrementa 1 después de evaluar evaluar

Así pues de las expresiones:

$var=5;print $var++;print "\n$var\n";

obtenemos impresos los números:

56

Debido a que en el primer print, incrementamos $var, pero al evaluar antes del incremento obtenemos su valor original, sin embargo, para evaluaciones posteriores su valor ha sido incrementado.

** Es el operador de Exponenciacion, así 5**2 resulta en 25 (5 al cuadrado)

* Es el operador de Multiplicación

/ Es el operador de División

% Es el operador de Modulo

+ Es el operador de Suma

- Es el operador de Resta

A estos no les dedicare mayores explicaciones por ser típicos en todos los lenguajes de programación y comunes a la matemática cotidiana.

Sin embargo, hay interesantes derivados de estos que vale la pena revisar. Estos son los operadores de automodificación, estos se implementan al observar que en muchos programas se

567

Page 568: Programacion en Java

requiere asignar a una variable el resultado de una operación que involucra el antiguo valor de la misma variable. Estos operadores proporcionan una forma resumida de operar una variable con otra y asignar a la primera el resultado, por ejemplo:

Si queremos hacer un $total, que sea el $total mas un $cargo particular podríamos escribir:

$total=$total+$cargo;

Y en Perl (al igual que en C) tenemos la opción de resumirlo como:

$total+=$cargo;

Nótese que el operador empleado fue "+=" que indica que al valor actual de la variable, se le asignara su propio valor mas el resultado de la evaluación de la expresión a al derecha del operador.

Así como se puede formar el operador de asignación con incremento, se pueden utilizar la mayoría de los otros operadores susceptibles a actuar sobre una variable, la única excepción no intuitiva es el de sustitución de patrones en la variable, que usa una sintaxis distinta "=~" que revisaremos al final de esta sección.

2.3.2- Operaciones Lógicas

Indispensables para las condiciones de todo ciclo de control son las operaciones lógicas en los lenguajes de programación, y en este contexto me permitiré incluir las funciones de comparación.

Las operaciones de comparación entre valores se dividen en aritméticas y de cadenas, y hay que tener especial cuidado en utilizar la familia correcta so pena de obtener resultados no deseados.

Operación Aritmética Cadena

Mayor que > gt

Menor que < lt

Mayor o igual >= ge

Menor o igual <= le

Igual == eq

Diferente != ne

Compara <=> cmp

La operación "Compara" regresa un valor de -1,0 o 1 dependiendo si el valor a la izquierda es menor, igual o mayor (respectivamente) que el de la derecha del operador, nótese que si deseáramos evaluar esto de forma lógica seria equivalente al operador "Diferente".

Debemos recordar que Perl considera como verdaderos los valores no nulos (cadenas) y diferentes de cero.

568

Page 569: Programacion en Java

Además, tenemos los operadores lógicos que permiten cambiar el valor de verdad de una expresión:

Operador Función

! Negación Lógica

- Inverso aditivo (cambia el signo de un numero)

~ Negación Bit a bit de un valor

&& Conjunción lógica de los operandos

|| Disyunción lógica de los operandos

& Conjunción bit a bit de los operandos

| Disyunción bit a bit de los operandos

^ Or exclusiva bit a bit

.. Si evaluado como escalar regresa alternativamente verdadero y falso. Como arreglo genera un arreglo a partir de los limites de un rango.

not Negación lógica de la expresión a su izquierda (muy baja precedencia)

and Conjunción lógica de baja precedencia

or Disyunción lógica de baja precedencia

xor Disyunción exclusiva lógica (precedencia mínima)

2.3.3- Operaciones con Cadenas

Así como los operadores aritméticos forzan a la variable a comportarse como de tipo entero o real, así los operadores de cadena forzan a los escalares a comportarse como cadenas, de modo que los números son automáticamente convertido en cadenas (son su precisión completa) al aplicarles estos operadores.

Las operaciones a realizar con cadenas, por lo regular se manejan como funciones en vez de como operadores, con la excepción de las siguientes:

Operador Función

=~ Especifica la expresión a afectar por búsqueda de patrones regulares (ver sección siguiente).

!~ Especifica la expresión a afectar por búsqueda de patrones regulares, pero evalúa negando el resultado lógico de la operación.

569

Page 570: Programacion en Java

. Concatenación, todas las cadenas operadas por "." forman una sola cadena.

x Es el operador de repetición, en contexto escalar toma la cadena de la izquierda y la repite el numero de veces que el numero de la derecha indica. En contexto de lista, toma la lista a su izquierda y la repite las veces que indica el operador a su derecha en una nueva lista.

++ Autoincremento, modifica el valor de los elementos de una cadena de modo que se incrementen sin cambiar su naturaleza, por ejemplo ++"zzz" -> "aaaa"; ++"99" -> "100" , ++"a9" -> "b0", etc.

2.3.4- Operaciones Regulares

Este sección se dedica a dar una rápida revisión a las operaciones de sustitución y reconocimiento de patrones (definidos por lenguajes regulares) que son de las características mas utilizadas y útiles de Perl, aun cuando no sean tan sencillos de comprender en un primer acercamiento, recomiendo al lector que dedique algún tiempo extra al estudio de la construcción de las expresiones para describir los patrones a buscar por su cuenta, pues puede obtener grandes beneficios de este esfuerzo, la referencia es hasta donde se, la mejor fuente de información a este respecto, en las secciones de operadores PERLOP y de expresiones regulares PERLRE.

Los operandos para utilizar las expresiones regulares son:

Operado Función r

=~ Especifica que la cadena a la izquierda del operando es la que pasara por el proceso ya sea de sustitución, o de búsqueda.

!~ Especifica que la cadena a la izquierda del operando será la afectada, y hace que la expresión en su conjunto regrese el negado del valor lógico resultante.

// Delimita un patrón regular.

Por defecto, las operaciones de búsqueda de patrones y sustitución actúan sobre el parámetro default $_ , y al evaluarlas modifican su valor y regresan el numero de sustituciones u ocurrencias encontradas.

La formación y manejo de las expresiones regulares escapa a los alcances de este documento, así que solo mencionare como utilizar estos operadores para realizar substituciones y su derivación para buscar algún patrón.

Para realizar búsquedas, la sintaxis básica es:

s/PATRÓN/CADENA/g

570

Page 571: Programacion en Java

Que hace la substitución de todas las ocurrencias de PATRÓN en CADENA. En esta forma, actuara sobre el parámetro default $_ y regresara el numero de substituciones que haga o cero si no encuentra el patrón.

Así, para cambiar todas la ocurrencias de "Hola" por "Saludos" en una cadena llamada $cadena (en vez de en $_) deberemos escribir:

$cadena=~s/Hola/Saludos/g;

Por el contrario, si deseamos saber cuantas veces aparece "Hola" en cadena y colocar este valor en $cuenta deberemos escribir:

$cuenta=( $cadena=~s/Hola/Hola/g );

Nótese que en realidad no realizo la operación de búsqueda (que si existe) sino la substitución por la misma cadena, para evitar introducir nuevas operaciones.

Por ultimo, si deseamos determinar si no se encuentra la cadena, (pues para determinar si se encuentra bastaría usar el valor de veces que se encontró la cadena como condición lógica, pues evaluara como verdadero si es distinto de cero y falso si es cero) usaremos el operador !~

if($cadena!~s/Hola/Hola/g){ print "No se encontraron \"Hola\"s\n";}

Como ya se menciono, para un tratamiento serio de las cadenas regulares y los operadores para utilizarlas hay que consultar la referencia de perl en las secciones de PERLOP y PERLRE.

2.3.5- Operaciones Misceláneas

A continuación describo brevemente la función de otros operadores que resultan útiles en condiciones particulares.

Operador Función

?: El operador condicional, es una forma de if resumida en una sola expresión, tiene tres operandos, op1?op2:op3 donde op1 es una expresión que evalúa falsa o verdadera, Si evalúa verdadera, se evaluara y la expresión regresara el valor de op2, si por el contrario, op1 evalúa falso, es op3 la que determina el valor final de toda la expresión.

, Como el operador , de C, si se usa entre dos expresiones, evalúa la de la izquierda, desecha el valor obtenido y evalúa la expresión de la derecha regresando el valor así obtenido.

=> Este operador funciona primordialmente igual que la coma, pero es exclusivo de Perl 5, forzando en algunas versiones que el operando de la izquierda sea una cadena (recuérdese su uso para declarar arreglos asociativos).

571

Page 572: Programacion en Java

Además, tenemos los signos de puntuación, que a diferencia de otros lenguajes son considerados operadores de Perl.

Básicamente, tenemos 4 tipos de símbolos que nos sirven para agrupar otros símbolos para darles una interpretación especial, son:

Símbolo Función

'' Especifica valores literales (cadenas), no hace substituciones.

"" Especifica valores literales, haciendo substituciones.

`` Ejecución, ejecuta el comando contenido entre ` después de hacer las substituciones indicadas.

// Suelen usarse para delimitar expresiones regulares, hace substituciones con la operación s//.

Entiendase por substitución el reemplazar el nombre de variables por los valores de estas, así como los caracteres de escape; por ejemplo:

$saludo="echo hola";print '$saludo'."\n";print "$saludo"."\n";print `$saludo`."\n";print "adios\n";

Genera como resultados algo similar a:

$saludoecho holaholaadiós

Como se puede ver , en todos los casos imprime un fin de línea "\n" después de cada cadena (o expresión), la primera, es, literalmente la cadena dada; la segunda, es la cadena donde se ha substituido el nombre de la variable $saludo por su valor; la tercera, es el resultado de la ejecución del comando especificado en la cadena, donde se substituyó el nombre de una variable por su valor.

Perl 4 no regresa valor cuando utilizamos las comillas para ejecutar un programa o comando, es decir, lo ejecuta sin capturar su salida, de modo que esta irá a la misma salida estandar del script que lo invocó.

Alguna experimentación con el uso de estos operadores es altamente recomendable, pues las convenciones de cada programa para entregar sus resultados a algún dispositivo o a alguna salida distinta de la standard pueden ocasionar resultados poco previsibles.

2.4- Estructuras de Control

Las estructuras de control básicas de Perl y su sintaxis son:

572

Page 573: Programacion en Java

IF:if (EXPRESIÓN) BLOQUEif (EXPRESIÓN) BLOQUE else BLOQUEif (EXPRESIÓN) BLOQUE elseif (EXPRESIÓN) BLOQUE ... else BLOQUEWHILE:WHILE (EXPRESIÓN) BLOQUEETIQUETA WHILE (EXPRESIÓN) BLOQUEFORfor (EXPRESION;EXPRESION;EXPRESION) BLOQUEETIQUETA for (EXPRESION;EXPRESION;EXPRESION) BLOQUEFOREACHforeach VARIABLE (LISTA) BLOQUE

EXPRESIÓN es toda instrucción de PERL que evalúa en un solo valor, básicamente se emplean como inicializaciones y condiciones de las estructuras.

BLOQUE es un conjunto de expresiones encerradas en {}, nótese que todos los bloques, sin excepción inician con { y terminan con }.

ETIQUETA se refiere a etiquetas en el código, estas son nombres seguidos de ":", que permiten identificar a algún bloque en particular.

Para modificar la ejecución de los ciclos tenemos las siguientes instrucciones:

next Esta interrumpe el flujo del ciclo iniciando de inmediato ETIQUETA la siguiente iteración. Si no se especifica la ETIQUETA afecta al ciclo mas interno.

last Esta interrumpe el flujo del ciclo y pasa el control a la ETIQUETA primera instrucción después del bloque del ciclo.

redo Esta reinicia la ejecución del ciclo, sin reevaluar la condición.

Los programas a lo largo de este documento son ejemplos del uso de estas estructuras de control, por lo que no pongo ningún ejemplo especifico en esta sección.

2.4.1- Manejo de subrutinas

Como en cualquier lenguaje estructurado, Perl tiene la capacidad de generar nuevas funciones o procedimientos, pero perl tiene varias peculiaridades en la forma de definir sus funciones,Ademas de ser estas las que sufrieron mas cambios entre la versión 4 y 5.

Características de las funciones en la versión 4 de Perl

Para su ejecución, se les tiene que señalar explícitamente, anteponiendo el símbolo de & al nombre de la función (algo similar a la clase de una variable), las referencias a una función eran siempre a través de typeglobs por lo que es incomodo e inusual usarlas. Reciben siempre un único parámetro, el arreglo default @_ en el cual debían ir todos los parámetros que de desearan, sin posibilidad forzar el paso de ciertos parámetros a la función, para obtener los valores se suele hacer un $parametro=shift @_; por parámetro.

573

Page 574: Programacion en Java

Características de las funciones en la versión 5.002 y superiores

Debe notarse que en versiones anteriores de perl no todas están implementadas, en particular, los prototipos de función no están implementadas sino hasta la versión 5.002.

Si un procedimiento o función ya ha sido declarado y se ejecuta como instrucción en una línea puede omitirse el símbolo & al inicio de su nombre. Es mas, siendo que perl no requiere el uso de paréntesis al inicio y final de una función, los parámetros pueden seguir al nombre de la función como para cualquier otra función nativa de Perl.

La verdadera utilidad de declarar nuestras funciones es el reducir la cantidad y complejidad de la revisión debe hacer de los valores que recibe como parámetros (dejando a Perl una mayor parte del trabajo), por ejemplo, si construimos una función que forzosamente requiera una clase de valor como parámetro (por ejemplo, una referencia) podemos definirla de modo que solo acepte parámetros que cumplan con esta característica, de modo que perl genere un error en caso de que traten de pasarle un dato de tipo distinto, en vez de que nosotros tengamos que verificar si el escalar recibido corresponde o no a una referencia.

A continuación revisaremos la definición de las subrutinas y funciones, Perl no hace mayor diferencia entre estas, de hecho se manejan igual y usaremos los términos indistintamente a lo lago de esta sección.

Sintaxis básica:

Declaraciones; especifican que habrá una función de este nombre pero no especifican su código, requieren que posteriormente se haga la definición.

sub NOMBRE; Con parámetros indeterminados (estilo Perl4)

sub NOMBRE(PROTOTIPO); Especificando clase de parámetros, Perl5.002 o

Definiciones Se especifica el código de una función, y si se desea, también su nombre y prototipo (tipo y numero de parámetros).

sub NOMBRE BLOQUE; Definición de la función, parámetros indeterminados

sub NOMBRE (PROTOTIPO) Definición con parámetros BLOQUE

$subref=sub BLOQUE Especifica código y regresa una referencia a el, en este caso $subref es una referencia a una función de parámetros indeterminados cuyo código esta contenido en BLOQUE. No lo discutiremos a detalle por considerarlo una aplicación especial de referencias y subrutinas.

Los elementos que empleo en esta definición son:

NOMBRE Es un identificador valido para perl (palabra formada de

574

Page 575: Programacion en Java

números, letras y bajo-guion con las mismas reglas que C, Pascal y demás. Es el nombre con el que se podrá invocar la función, si se omite, en vez de definir o declarar la función se generara una referencia a una función anónima.

BLOQUE Es un bloque de código, al igual que en los ciclos de control, siempre inician con "{" y terminan con "}", dentro, puede haber cualquier serie de sentencias de perl, y determina un espectro de validez para las variables que se declaren locales con my.

PROTOTIPO Es una cadena que indica la clase y una característica de cada parámetro que la función recibirá.

Comencemos revisando como crear un procedimiento que no tome parámetros.

sub sencillito{ print "Yo soy un procedimiento sencillito\n";}

Este procedimiento se invocara con el identificador "sencillito" o "&sencillito" , ejecutara su código y terminara sin regresar un valor. Una versión de este que ahora es función (pues regresa un escalar) será:

sub sencillo{ print "Yo soy una función sencilla\n"; return "sencilla"; die("no me muero\n");}

Este procedimiento, regresa la cadena "sencillo" después de imprimir el mensaje especificado en el print, nótese que despues del return no continua su ejecución y que al igual que "sencillito" no pone atención a los parámetros, que podrían de hecho, pasársele o no.

return es la palabra reservada que hace que un procedimiento o función termine y regrese un valor, si se usa el return sin especificar valor o si se llega al final del código sin encontrar return regresa un nulo.

Si deseamos tomar parámetros, pero no aun hacer uso de los prototipos, debemos tomar en cuenta que todos los parámetros vienen empacados en @_ el arreglo default, y que para obtenerlos podemos usar las siguientes expresiones:

($primero,$segundo,$tercero)=@_; #Obtiene tres escalares del arreglo (nótese que si tercero fuese arreglo, absorbería el resto de los escalares en @_)$primero=shift @_;$segundo=shift @_;$tercero=shift @_; #También obtiene tres parámetros de @_, pero este también los retira de @_, pues el operador shift toma el primer elemento de una lista y lo elimina, regresándolo como resultado de la expresión.

575

Page 576: Programacion en Java

La ventaja del segundo método es que se retiran de @_ los elementos que eran parámetros para la función que hacemos, de modo que @_ puede contener mas información útil para algún otro propósito.

Las referencias son de gran utilidad para manejar todo tipo de datos como parámetros a una función, de hecho, la única posibilidad de pasar un arreglo a un procedimiento sin que el arreglo pierda su identidad requiere el uso de referencias, aunque como veremos, este requisito puede esconderse al invocar la función (no así al implementarla).

De cualquier modo, si recibimos referencias como parámetros solo deberemos darles el manejo de escalares para extraerlas de @_ y usarlas como siempre.

La manera de "ocultar" la necesidad de referencias en el momento de invocar la función implica el uso de prototipos.

Los prototipos pueden ser de gran utilidad para evitar errores de programación al invocar funciones con los parámetros equivocados, por lo que a pesar de no ser un tema básico lo incluyo en este documento.

Las cadenas del prototipo se componen de los siguientes símbolos:

$ Escalar

@ Arreglo (toma todos los parámetros siguientes en el arreglo)

% Arreglo Asociativo (también absorbe todos los parámetros)

\ El identificador del siguiente parámetro deberá iniciar cón ($,@ o %).

; Los siguientes parámetros son opcionales

Así por ejemplo, las siguientes definiciones especifican:

sub biescalar ($$); #recive dos escalaressub escarreglo ($@); # toma el primer parámetro individual y todos los demás en un arreglo.sub tomahash (%); #los parámetros recibidos forman un hashsub dosotresesc ($$;$); #dos escalares obligatorios y un tercero opcional.sub arreglos (\@$\@); #un arreglo, un escalar y después otro arreglo, sin que se mezclen.

Debe notarse que si se utilizan los parámetros de clase arreglo y hash sin "\" estos serán el ultimo parámetro a recibir, pues tomaran el resto de los parámetros dentro de si, de modo que si se desea tomar mas de un arreglo o hash como parámetro se deberá especificar la clase con "\". Nótese que el uso de "\" tiene como efecto colateral que se recibe una referencia al arreglo, hash o escalar, la que esta garantizada a ser una referencia al tipo especificado; nótese que no por eso el parámetro que acepta la función es una referencia, por el contrario, debe ser una variable de la clase especificada.

Este mecanismo, nos permite controlar la clase de la variable que nos dá el velor que recibimos como parámetro.

576

Page 577: Programacion en Java

A continuación, veremos un ejemplo sencillo del uso de prototipos y funciones en un programa que especifica una función que debe recibir dos escalares e imprime un saludo con ellos.

#!/usr/bin/perlprint "hola\!\n";sub rutina ($$){ $nombre=shift @_; $direccion=shift @_; print "Hola $nombre\nVivies en $direccion\n"; return 345;}

print "Dame tu nombre\n";$res[0]=<STDIN>;chop $res[0];print "Dame tu direccion\n";$res[1]=<STDIN>;chop $res[1];print rutina $res[0],$res[1] ."\n";

Revísese con cuidado este programa y experiméntese con el, de modo que el siguiente programa (una modificación del anterior que explota las características de prototipos y de referencias para recibir dos arreglos y un escalar como parámetro) pueda comprenderse con facilidad.

#!/usr/bin/perl# Definición de la subrutina "rutina"sub rutina(\@\$\@){ ($runo,$rsaluda,$rdos)=@_; #(@uno+0>@dos+0)?print "uno mayor que dos\n":print "dos mayor o igual a uno\n"; print "$$runo[0],\n"; print "$$rsaluda\n"; print "$$rdos[0],$$rdos[1],\n"; return 345;}

#Programa principal#Primer nombre@res=("Daniel","Sol","Llaven");#Segundo nombre@re=("David","Jonathan","Sol","Llaven");$algo="hola";print rutina(@res,$algo,@re) ."***\n";

Este segundo programa define una función con prototipos que utilizan extensivamente "\", de modo que no solo requiere que sus parámetros sean un arreglo, un escalar y un segundo arreglo, sino que estos deben ser variables de las clases @, $ y @ respectivamente (por ejemplo, no aceptara una cadena constante en vez de $algo en la ultima línea).

Observando el código, vemos que los parámetros se reciben como referencias y se usan como tales, pero que en el programa principal se pasan los parámetros como variables de los tipos especificados en el prototipo (de hecho, "rutina" no aceptará ninguna otra cosa).

Espero que este ejemplo ayude a entender los conceptos de prototipos y de subrutinas, en realidad, no suele hacerse hincapié en estos temas por no ser necesarios, dada la libertad que perl

577

Page 578: Programacion en Java

nos da, pero en el contexto de desarrollos medianos a grandes pueden ser muy valiosos para evitar errores de difícil detección.

En este capitulo, como en todos los demás, es indispensable que el lector haga programas de ejercicio de su propia inspiración con el tiempo y la libertad de probar y experimentar los temas que revisamos aquí.

2.5- Operaciones con archivos

Al menos en la UNAM, Perl se utiliza principalmente con dos propósitos, el primordial es para implementar interfaces para servicios de red, y el segundo, es el proceso sistemático de grandes archivos de texto (bitácoras de operación, control de usuarios, etc.) debido a su facilidad para el manejo de archivos y si flexibilidad en el manejo de datos capturados de textos (formato muy común de la información en UNIX, que tiene la única desventaja de hacer crecer un poco mas los archivos, con las ventajas de ser interpretables con herramientas mas variadas, e incluso a simple vista, con la posibilidad de comprimirse con gran eficiencia).

Debo aclarar desde ahora, que el manejo de archivos binarios, no es uno de los puntos fuertes de perl, y que aunque puede hacerlo, no solo no es común, sino que resulta mejor utilizar programas hechos en C para proveer la manipulación de este tipo de archivos, es por esto, que este tema solo lo tocaremos brevemente, y nos enfocaremos al uso de archivos de texto.

Las operaciones básicas para trabajar con archivos son:

open(VARCH,ARCHIVO);<VARCH>;close(VARCH);

Que respectivamente abren, leen, y cierran el archivo. Donde:

VARCH Es la variable que identificara al archivo, no se le pone identificador de clase y por convención, se utiliza con mayúsculas.

ARCHIVO Es el nombre del archivo a utilizar, precedido de símbolos que indican el modo de uso del archivo.

2.5.1- Apertura y cerrado

Los símbolos con los que especificamos el modo de operación de un archivo son:

Símbolo Significado

> El archivo se abrirá para escritura (borrándolo si existe)

< El archivo se abre para lectura

>> El archivo se abrirá para escritura agregando al final

+< El archivo se abrirá de lectura escritura

+> El archivo se abre para lectura y escritura vaciandolo primero

578

Page 579: Programacion en Java

El uso de archivos de lectura y escritura lo revisaremos mas adelante, pues involucra algunos procesos adicionales al manejo convencional.

Los "archivos" que no requieren ni abrirse (ni cerrarse) son, como es de esperarse, la entrada estándar, la salida estándar y la salida de errores, sin embargo, si pueden ser cerrados y abiertos con open y close, para esto se utilizan como nombre de los archivos (sin mas símbolos):

>- Abre la salida estándar

- Abre la entrada estándar.

Si no se utiliza ningún símbolo sino solo el nombre del archivo a utilizar, este archivo se abre para lectura.

La función open regresa al evaluarse el valor de la variable de archivo, o nulo si no se puede abrir el archivo en el modo deseado, de modo que si se utiliza como condición lógica resultara verdadera en caso de lograr abrir el archivo.

Algunos ejemplos de apertura de archivos son:

open(ETCP,"</etc/passwd");

Apertura de lectura únicamente del /etc/passwd asociado a la variable de archivo ETCP

open(SAL,">salida.txt");

Creación del archivo "salida.txt" en el directorio desde donde se invoque el programa, solo de escritura y asociado a la variable SAL.

El cerrado de los archivos en una operación recomendable, pero no necesaria, en el caso de los archivos, garantiza que todos los buffers han sido vaciados (o regresa falso en caso de que haya algún error).

2.5.2- Lectura de datos

La lectura de la información contenida en los archivos, como ya habíamos mencionado antes, se realiza con la operación <>, en esta ocasión la veremos mas a detalle.

El operador <> realiza la lectura de texto de el archivo, cuya variable se coloca entre < y >. Ahí es evaluado en un contexto de escalar, regresa un renglón, incluyendo el carácter de fin de línea. Si se evalúa en contexto de lista, regresa un arreglo de todos los renglones del archivo incluyendo sus terminadores de línea.

A manera de ejemplos:

579

Page 580: Programacion en Java

$reng=<ENTRADA>;

Carga una línea del archivo asociado a ENTRADA (que debe ser de lectura).

@contenido=<ENTRADA>;

Toma todas las líneas del archivo y las coloca como elementos consecutivos del arreglo @contenido.

Debe mencionarse, que cuando se evalúa el operador <> para un archivo, el puntero de posición en el archivo va avanzando, de modo que en aplicaciones sucesivas (de contexto escalar) recibiremos renglones sucesivos. Y si se aplica en contexto de arreglo, una evaluación sucesiva no regresara mas renglones.

Además, el operador <> tiene la propiedad de tomar valor por defecto cuando no se especifica que archivo debe usar, esta característica tiene dos modalidades:

Usar el ultimo archivo abierto:

El operador <> continua trabajando con el ultimo archivo al que reavisamos, por ejemplo:

open(ENT,"<entrada.txt"); #trabaja con el archivo entrada.txtwhile(<>) #para toda línea del ultimo archivo abierto{ print; # se imprime.}

En este programa utilizamos varios parámetros default de las funciones:

en el while(<>) vemos que el operador <> no tiene ningún identificador de archivo, y sin embargo funciona como si se hubiese puesto <ENT> en vez de <>, esto es debido a que <> toma por default al ultimo archivo abierto como archivo default.

En segundo lugar, vemos que ni asignamos el valor que regrese <> a ninguna variable ni especificamos que variable debemos imprimir en el print. En ambos casos se esta utilizando el parámetro default $_, <> por defecto asigna el valor que resulte a $_ y print por defecto imprime a la salida estándar el valor de $_.

El otro modo de obtener los valores de archivos por defecto a trabajar con <>; supongamos un programa de nombre imprime.pl con el siguiente código:

while(<>) #para toda línea del archivo default{ print; #imprimirla}

Aparentemente este programa carece de función pues no especifica ni abre ningún archivo para leer, el modo de especificar que archivo debe usarse es por medio de parámetros, de modo que el programa debe ejecutarse como:

imprime.pl entrada.txt

580

Page 581: Programacion en Java

Para que imprima todas las líneas del archivo entrada.txt.

En resumen, si no se especifica cual archivo deberá de usarse para leer información con la operación <> Perl tratara de utilizar:

1. El ultimo archivo abierto por open 2. El siguiente parámetro como nombre de archivo a abrir y procesar como de lectura.

Nótese que si al segundo programa, imprime.pl, se le da mas de un parámetro, recorrerá todos los parámetros como nombres de archivos a abrir para usar con <>. Esto provee un método muy breve para hacer programas que procesen los archivos cuyos nombres se pasen como parámetros.

Deba tenerse cuidado al emplear las variables de default en un programa, y no recomiendo que se usen en programas grandes o de lógica compleja, pues estos valores son fácilmente afectables por otras operaciones y se requiere un conocimiento muy profundo de perl para conocer todas las peculiaridades del lenguaje en cuanto a estas variables especiales. Así pues, recomiendo usarlas solo para hacer mas simples los programas que ya sean sencillos por si mismos.

2.5.3- Escritura

Una vez que hemos abierto un archivo para escritura, la forma mas común de escribir en el, es mediante la función print, de hecho, print esta diseñada para escribir a archivos, pero el archivo que utiliza por defecto para escribir es el de la salida estándar STDOUT, la sintaxis para especificar que archivo debe utilizarse es:

print ARCHIVO LISTA

donde:

ARCHIVO es la variable de tipo archivo donde se dirigirá la salida

LISTA es una lista con los valores a imprimir, (si solo es un escalar se interpreta como una lista de un solo elemento, pero evita que al imprimir listas se obtengan los números de su cardinalidad en Perl 5).

Si se desea emplear las variables de archivo guardadas en arreglos, hashes o como resultado de expresiones se les deberá poner en un bloque de código (entre {}) para mas información consultar la función print en la referencia de Perl (sección PERLFUNC).

Así pues, para crear y llenar un archivo con los primeros 100 números enteros:

open(AS,">enteros.100");for($c1=0;$c1<100;$c1++) # de 0 a 99 (perdón si 0 no es entero){ print AS "$c1\n"; #escribe al archivo de AS el numero.

#contenido en $c1 y fin de línea.}

Nótese que si se usan paréntesis alrededor de los parámetros del print muy probablemente Perl pensara que toda la expresión entre paréntesis especifica el valor a imprimir resultando en un error, por lo que recomiendo se evite el uso de paréntesis cuando se usa print para escribir a archivos.

581

Page 582: Programacion en Java

2.5.4- Saltos y proceso binario

Antes que nada debo indicar que las funciones que revisaremos aquí las incluyo, no por considerarlas básicas para la programación, sino por su gran utilidad, y para que se conozca el modo en que perl puede lidiar con archivos binarios.

Sin embargo, si se pretende desarrollar algo usando estas herramientas recomiendo ampliamente una saludable dosis de experimentación y prototipaje para evitar demoras posteriores, además, será indispensable tener la referencia a su lado.

Así que siéntase el lector con la libertad de saltar esta sección y continuar con la 2.6, que le resultará mas útil al corto y mediano plazo.

Comencemos por la forma en que perl 5 (todo lo que tratare en esta sección no funciona o funciona de modo muy distinto en perl 4) puede leer, escribir y manejar información binaria, que llamaremos empaquetada:

Perl maneja la información en tipos abstractos, que nos ahorran gran trabajo de programación al momento de usarlos, desafortunadamente, esto no ayuda en el momento de querer leer tipos de datos que vienen como una serie de bits dentro de bytes. La solución que perl da a este problema es mas o menos sencilla, pero requiere algún entendimiento de ,que es una cadena.

Una cadena es una serie de caracteres, como el lector debe ya saberlo, pero cada carácter es en sí mismo representado por un byte; así que si no somos muy exigentes con los caracteres que pueden o no ser incluidos en una cadena, podríamos decir que cualquier serie de valores representados en bytes pueden interpretarse como una cadena. Pues bien, este es el concepto que Perl explota.

Un empacado de información lo manejaremos como un escalar que para fines prácticos es de tipo cadena. Pero que en vez de generar con el operador "." y contener texto común y corriente, contiene una serie de caracteres (que no necesariamente producirán resutlados coherentes si tratamos de imprimirlos) que en si mismos contienen información que nos interesa recuperar.

El proceso de desempacado de la información consiste en indicar a perl que a partir de una cierta cadena empacada debemos extraer un arreglo de valores a partir de un formato que deberemos especificar. La contraparte de este proceso es el generar nuestra cadena empacada a partir de un arreglo de valores y la misma información de formato que pretendemos leer después.

Bajo este enfoque, Perl puede manejar perfectamente bien un archivo basado en registros, pero tendría serias dificultades para manejar un archivo que no codifique toda su información en un patrón fijo, para esto será muy importante la capacidad de brincar en la posición del archivo.

La sintaxis de las funciones que empaquetan y desempaquetan la información son las siguientes:

pack($FORMATO,@VALORES);

Esta función regresa la cadena empacada con el formato descrito por la cadena $FORMATO que contiene los valores presentes en el arreglo @VALORES.

unpack($FORMATO,EXPRESION);

582

Page 583: Programacion en Java

Esta es la contraparte de pack, usando el formato especificado por $FORMATO extrae los valores de la cadena que resulte de EXPRESIÓN (probablemente la variable que contiene la variable) y regresa al evaluarse un arreglo de valores de los tipos especificados.

Para especificar el formato en el que se empaquetara o con el cual desempaquetar la información se usa la siguiente sintaxis:

Simbolo Significado

A Cadena ASCII rellenada con espacios a la derecha.

a Cadena ASCII rellenada de nulos a la derecha.

b Cadena de bits (Orden ascendente de bits)

B Cadena de bits (Orden descendente de bits)

h Cadena de nibbles (en hexadecimal con el nibble bajo primero)

H Cadena de nibbles (en hexadecimal con el nibble alto primero)

c Un valor de carácter con signo

C Un valor de carácter sin signo

s Un entero corto con signo

S Un entero corto sin signo

i Un entero con signo

I Un entero sin signo

l Un entero largo con signo

L Un entero largo sin signo

f Un punto flotante en formato nativo de Perl

d Un doble en formato nativo de Perl

x Un byte nulo

X Regresar un byte

@ Llenar de nulos hasta la posición absoluta

Después de cada uno de estos se puede colocar un numero que indicara cuantos elementos de este tipo esperamos, a excepción de los del primer bloque (cadenas) donde el numero indicara la longitud de la cadena a interpretar como un solo elemento.

Todos estos especificadores funcionan tanto a la entrada como a la salida (e idealmente usamos la misma cadena de formato en el empaquetado y en el desempaquetado.

583

Page 584: Programacion en Java

Así por ejemplo, para leer de una cadena empaquetada los bits, suponiendo que estos estar ordenados del menos significativo (al principio) al mas significativo (al final), y que buscamos leer 32 bits será:

($cadbit)=unpack("b32",$paquete);

Nótese que al escribir ($cadbit) estamos asignando al escalar $cadbit el primer elemento del arreglo generado por unpack, es decir, el primer elemento generado.

Por otro lado, para empaquetar y desempaquetar en una cadena 4 enteros podemos escribir:

$paquete=pack("i4",(34,-43,127,512));@enteros=unpack("i4",$paquete);

Debe recordarse que con las cadenas el numero que se pone a continuación del carácter (aAbBhH) indica la longitud de las cadenas, pero con los demás tipos indica cuantos elementos de ese tipo esperamos.

Ahora, para leer byte por byte un archivo (los cuales podemos concatenar con los el acostumbrado operador ".") utilizamos la función getc

getc ARCHIVO;

Esta función regresa el siguiente carácter del archivo al que ARCHIVO hace referencia, o una cadena vacía (falso lógico) si llegamos al fin de archivo.

Por ejemplo, hagamos un diminuto programa que nos muestre los valores hexadecimales y binarios de todos los bytes de un archivo:

#!/usr/bin/perl#programa de prueba de las capacidades de desempaquetado #y proceso binario de archivos en Perl5open(AE,"<$ARGV[0]");$contador=0;print "offset\thexa\tbinario\t\tcaracter\n";while($ch=getc(AE)){ $binario=unpack("B8",$ch); #deseamos el bit mas significativo a la izquierda $hexa=unpack("H2",$ch); #nibble mas significativo primero print "$contador\t$hexa\t$binario\t\"$ch\"\n"; #$ch es el carácter original $contador++;}close(AE);

Este programa también intenta imprimir el carácter leído con fines didácticos, si se emplea con archivos auténticamente binarios recomiendo que se elimine $ch de las variables a imprimir.

La característica comúnmente utilizada del proceso de archivos binarios que resta por revisar es la lectura aleatoria de archivos; esto se logra cambiando arbitrariamente la posición del apuntador de archivo mediante la función search. Esta función trabaja por bytes, de modo que es de muy poca utilidad cuando trabajamos con archivos de texto. La sintaxis de la función es:

584

Page 585: Programacion en Java

search ARCHIVO,POSICION,MODO

donde:

ARCHIVO es la variable con la que hacemos referencia a un archivo

POSICIÓN Especifica el avance desde la posición que indique el modo

MODO 1-Desde el inicio, 2-Desde la posición actual, 3-Desde el final.

2.6- Operaciones con recursos del sistema

Una de las grandes ventajas de Perl sobre otros lenguajes es la facilidad con la que interactúa con otros programas y con el sistema (UNIX en particular pero cualquiera en lo general). De hecho, una de las grandes mejoras del Perl5 sobre el Perl4 es que tiene un proceso mucho mejor coordinado de programas concurrentes invocados desde Perl, por lo que recomiendo ampliamente se considere el desarrollar programas que usen intensivamente el sistema con Perl5 (o superior).

Por considerar esta solo una introducción planteare solo las formas mas básicas de interacción con el sistema, recomiendo ampliamente, que para las tareas especificas para las que se suelen usar llamadas al sistema se consulte la referencia de Perl en la sección PERLFUNC para averiguar si están presentes y cual es la manera de usarlas.

En esta sección revisaremos tan solo los métodos de ejecución de programas comandos del sistema operativo que dividiremos en tres categorías:

• Como expresiones a evaluar: Ya lo hamos revisado en la sección de operadores, pero conviene repetirlo en este contexto.

• Sin interrelación: Invocación de comandos y programas esperando su culminación antes de terminar, o de modo que sean la ultima acción de nuestro programa.

• Entubados: Principalmente mediante el uso de open y close, nos permiten alimentar de información otros procesos o alimentar nuestro programa del resultado de ellos.

2.6.1 Como expresiones

Como ya vimos, con el uso de las comillas `` podemos tratar un comando del S.O. como si fuese una expresión la cual nos regresa los resultados que dicho comando entrega a su salida estándar.

algunos ejemplos de esto son:

`date`

evalua como "Thu Sep 12 18:49:47 CST 1996\n" suponiendo que esa fuese la fecha

`wc -l Hola.pl`

evalua como " 3 Hola.pl" suponiendo que Hola.pl tuviese tres líneas.

585

Page 586: Programacion en Java

Es la forma mas sencilla de ejecutar comandos desde nuestro programa en Perl.Debe aclararse que Perl 4 solo ejecuta el comando dejando la salida estandar de este a la salida del script de Perl; de modo que no regresa ningun valor a nuestro script de Perl 4.

2.6.2 Sin interrelación

Por interrelación entiendase que: Ya sea que la entrada estándar del proceso ejecutado sea tomada desde nuestro programa. O que nuestro programa tome la salida del comando, de modo que en estos métodos la salida estándar del programa ejecutado será la salida estándar que nuestro propio programa tiene (consola por lo regular) y lo mismo para la entrada estándar.

Hay dos métodos para invocar programas con estas características "system" y "exec", y la diferencia entre estos radica en el ambiente que el programa invocado ocupara. La sintaxis de estas es:

system(PROGRAMA);

Ejecuta PROGRAMA como un nuevo proceso, espera a que acabe y continua

exec(PROGRAMA);

Ejecuta PROGRAMA, y termina nuestro script siendo reemplazado por el PROGRAMA que ejecutamos.

donde PROGRAMA es una cadena conde esta el comando (como lo daríamos en S.O.) a ejecutar, puede ser un comando compuesto, por ejemplo:

$comando="cat Hola.pl |grep daniel";system($comando);#equivalente a system("cat Hola.pl |grep daniel");

efectivamente escribirá en la salida estándar las líneas del script Hola.pl en las que aparezca la palabra "daniel".

Cuando un programa se ejecuta, las variables de ambiente, el camino actual y otras condiciones del sistema operativo suelen ser determinantes para su desempeño, cuando utilizamos system, el programa a ser ejecutado se ejecuta en un nuevo ambiente (que no tiene mas relación con el nuestro que gozar de los mismos privilegios), mientras que cuando lo ejecutamos con exec el programa de hecho substituye a nuestro script de perl en el ambiente, de modo que hereda todas las características que nuestro script haya determinado del ambiente.

De este modo, una posible aplicación de exec es cuando debemos forzar que el programa que ejecutamos tome variables de ambiente que colocamos con el script de perl, en cambio system es necesario cuando debemos ejecutar mas de un programa, o cuando debemos realizar mas acciones con nuestro script una vez que el programa invocado termine.

2.6.3 Entubados

En esta sección, veremos como podemos capturar la salida que los programas que ejecutemos generan y procesar esta como si fuese un archivo y por otra parte, como podemos asignar una variable de archivo a la entrada estándar de un programa que ejecutemos para que procese lo que le alimentemos.

586

Page 587: Programacion en Java

Una nota importante: Cuando ejecutamos programas de este modo Perl no espera a que el programa termine para continuar, si deseamos que lo haga debemos usar close con la variable de archivo que les asociemos para indicar que el programa debe terminar (y perl efectivamente esperara a que lo haga).

La ejecución de comandos o programas asociándolos a archivos se hace añadiendo el carácter "|" al comando a ejecutar y utilizando el comando open.

Por ejemplo, supongamos que deseamos procesar el resultado de un programa llamado "fuente". Si el comando para su ejecución desde sistema operativo fuese:

fuente saluda 3

deberemos "Abrirlo" de la siguiente forma:

open(RESF,"fuente saluda 3|");while(<RESF>){ #procesar los renglones que va poniendo en $_}close(RESF); #espera a que termine y cierra el flujo de datos

Nótese como se coloco el carácter "|" al final del comando usual. Y que RESF funciona como un archivo abierto para lectura. También es conveniente recalcar que si deseamos ejecutar un comando que per se utilice entubamientos se puede hacer, por ejemplo:

open(ALGO,"fuente hola 3|prcesa_hola |");

Asociara a ALGO lo que el programa procesa_hola genere en su salida estándar, mientras que el S.O. ha entubado la salida de "fuente" a la entrada de "procesa_hola".

Del mismo modo, si deseamos poner información a la entrada estándar de un comando deberemos abrirlo colocando el símbolo "|" como primer carácter del comando. Y el archivo que asociemos se deberá usar como una archivo abierto para escritura.

De nuevo, cuando indiquemos el close, Perl enviara un EOF al archivo al que estamos ejecutando y esperara a que este le indique el termino de su proceso.

3- PROGRAMACIÓN ESPECIAL

En la Seccion 2, revisamos la programación básica en Perl, pero la programación en este lenguaje se basa, sobretodo, en gran cantidad de optimizaciones y formas alternativas de realizar tareas comunes lo que le dá una funcionalidad adicional que lo hace en extremo valioso en la realización de estas tareas o aquellas que las requieran.Por eso es que dedico toda una sección de este tutorial a tratar estas características y el modo de emplearlas, para que el nuevo programador, no solo pueda elaborar programas en Perl, sino que pueda explotarlo para facilitar la realización de sus tareas mas usuales.

3.1- Uso de Perl en línea, Tareas Comunes

587

Page 588: Programacion en Java

Una de las características de Perl dada su semi naturaleza de interprete es que puede ser ejecutado y actuar como un interprete de comandos (aunque los ejecuta hasta el momento de terminar todas las instrucciones con un fin de archivo desde teclado.

Además, Perl tiene varias opciones en línea que son de extraordinaria utilidad, por ejemplo, para substituir cadenas a lo largo de todo un archivo, o indicar que los errores se expresen en un formato mas entendible (en caso de que no este como default desde que lo compilamos), etc.

Esta sección, se dedica a revisar algunos de estos parámetros de Perl enfocado a usos específicos.

3.1.1- Formas de Especificar el programa

Existen tres métodos de indicar a Perl cual ha de ser el script a ejecutar:

1- Ejecutando un script directamente que especifique la colocación de Perl con una línea que inicie con "#!"

2- Pasando el script por la entrada estándar a Perl (completo con todo y fin de archivo), Pero esta opción no admitirá parámetros para el script.

3- Especificado las instrucciones en línea de comando con el interruptor -e. Esta la revisaremos con cuidado cuando veamos los interruptores.

3.1.2- Interruptores

En esta sección no intento revisar todos los interruptores descritos en la sección PERLRUN de la referencia, sino solo algunos de ellos enfocados a comprender mejor el como se realizan algunas tareas con Perl en línea de comando, y el orden también ha sido modificado tratando de hacer mas fácil su comprensión.

-e comando

Este interruptor se usa para indicar una línea de código en Perl a ejecutar (nota solo una, completa con ";"), si se da este interruptor, Perl no buscara un nombre de script en la lista de argumentos ,ni la recibirá por la entrada estándar. Para formar un script de varias líneas, se puede dar varias veces este interruptor con líneas distintas.

-n

Hace que Perl asuma que existe el ciclo siguiente alrededor del script que le damos:

while(<>){ #aqui va el script}-p

Al igual que "-n" hace que Perl asuma un ciclo alrededor del script, pero además, hace que imprima automáticamente el ultimo valor evaluado de cada ciclo del script, lo que equivale al siguiente ciclo:

588

Page 589: Programacion en Java

while(<>){ #El script va aquí} continue{ print;}

El uso del interruptor -p inhibe al interruptor -n.

-a

Al estar usando un interruptor "-e" o "-p" hace que los renglones que se van leyendo en el ciclo del "-n" o "-p" pasen por un "split" con el delimitador especificado con el interruptor -F y sean asignados, ya como arreglos a @F, resultando en la siguiente estructura del while:

while(<>){ @f=split(' '); #aqui va el script}-F

Con este interruptor determina una expresión regular la cual se usara como delimitado para el split del switch "-a", se puede poner el patrón entre "/" siendo estos ignorados. Nota: los interruptores -a y -F son exclusivos de Perl 5.

-iextencion

Especifica que los archivos procesados por "<>" sean editados en su lugar, esto es, que el archivo de entrada sea movido a otro archivo con la extensión especificada, y que en el archivo original se vayan escribiendo los resultados que generemos. Si no se especifica ninguna extensión, al final del proceso no habrá ningún archivo con la información original.

-v

Imprime la versión y nivel de parches del ejecutable de Perl.

-w

Imprime advertencias sobre identificadores que se usen solo una vez, escalares que se usan sin haber recibido valor, subrutinas indefinidas, archivos sobre los que intentemos de realizar operaciones para las que no fueron abiertos, etc. Se recomienda para diagnosticas problemas en los programas.

Habiendo revisado algunos interruptores, ahora vamos a ver algunos ejemplos de las cosas que podemos hacer con ellos.

3.1.3- Tareas Comunes de Perl en Línea de Comando

Entiéndase por Línea de Comando, que desde nuestro interprete de comandos, invocamos Perl con los parámetros adecuados para que realice una función sin necesidad de haber escrito un script, por supuesto, no son tareas muy complejas, pero llegan a ser deslumbrantemente útiles.

589

Page 590: Programacion en Java

3.1.3.1- Reemplazar una cadena en un archivo por otra

Es muy común que, por ejemplo, deseemos reemplazar todas las ocurrencias de, digamos, un camino y nombre de programa, por otro en un archivo de texto que podría ser el código fuente de un programa o alguna otra cosa, muchos procesadores de palabra soportan este tipo de procesos, pero con Perl no se requieren herramientas extra y podemos trabajar de un solo golpe grandes cantidades de archivos.

el comando seria de la forma:

Perl -pi.bak -e "s/CADENA1/CADENA2/g;" archivo1 archivo2 archivo3

Recordando lo que significan los interruptores:

-p indica que alrededor de la instrucción tenemos un ciclo que revisara todas las líneas de todos los archivos que se pasen como parámetros a script, además, que los resultados se imprimirán (a la salida estándar). Como este interruptor no recibe valores, puede acompañarse de otro interruptor (en este caso, "-p -i" es equivalente a "-pi").

-i Los archivos procesados serán editados en línea, generando respaldos de estos con la extensión .bak, la salida estándar del bloque de "-p" se redirecciona a los archivos origianles, de modo que los resultados del script serán el nuevo contenido de los archivos originales.

-e El comando a ejecutar dentro del ciclo, y cuyo resultado será el nuevo contenido de los archivos es una substitución (usamos implícitamente el argumento default $_ para indicar sobre que se hace la substitución), el comando, es una substitución del patrón en toda la línea leída.

archivo1 archivo2 archivo3 Son los archivos que usa el ciclo de "-p" que, como en el caso de cualquier otro script que usa "<>" son recibidos como parámetros.

El resultado final es que generamos archivo1.bak, archivo2.bak, archivo3.bak y archivo1, archivo2 y archivo3 y en los últimos la CADENA1 ha sido reemplazada por CADENA2.

3.1.3.2- Imprimir algún campo de un archivo

En UNIX, gran parte de la información del sistema se almacena en archivos de texto con separadores para indicar campos, siendo cada renglón un registro, además, como un ejemplo sencillo, planteemos una ejecución en línea de Perl que imprima todos los logins del etc/passwd que pertenezcan a un cierto grupo, digamos, el 100.

Perl -anF: -e 'if($F[3]==100){print "$F[0]\n";}' /etc/passwd

590

Page 591: Programacion en Java

Curso de Clipper

A principio de los años ochenta, DBASE II hizo su aparición de la mano de George Tate (1943-1984) y su empresa Ashton-Tate. Esta nueva herramienta se presentaba en el emergente mundo de los microordenadores con la intención de facilitar la gestión de las bases de datos.

Evidentemente, los sistemas de gestión de bases de datos existían desde mucho antes, sobre todo, desarrollados para grandes sistemas, pero la cuestión estaba en cubrir una carencia que más tarde o más temprano debía ser atendida por los ingenieros de software y que era esperada ansiosamente por el creciente número de usuarios de los ordenadores personales.

El sistema de gestión de bases de datos había que diseñarse no exclusivamente como un entorno de programación, semejante a otros entornos o lenguajes con capacidad de tratamiento de grandes masas de datos. Este debía posibilitar la ejecución interactiva de instrucciones, ser amigable, accesible por usuarios no programadores, y debía estar formado por unas instrucciones potentes y fáciles de memorizar.

(LA PRIMERA DE LAS VERSIONES DE DBASE II SE UTILIZÓ CON EL SISTEMA OPERATIVO CP/M, SIGUIÉNDOLE OTRAS COMO LA 2.4 DE SEPTIEMBRE DE 1983 BAJO DOS 1.1 Y 2.0).

También, a principio de los ochenta se comienza a utilizar entre los usuarios de micros una nueva terminología informática de bases de datos, ésta era más familiar en otros ambientes informáticos y definía con precisión los conceptos más básicos:

• Una base de datos puede definirse como la agrupación útil y organizada de información.• Bases de datos relacionales. Este tipo de estructura define relaciones entre los datos en

una base de datos. Un modelo simple organiza la base de datos de igual forma que podemos definir una tabla de dos dimensiones (filas y columnas). Los datos de una fila (registro) se subdividen en columnas (campos). A cada fila se la asigna un número (nº de registro) que representa el orden en que será almacenado el registro en la base de datos. A las distintas columnas se le asignará un nombre de campo. Con esta estructura básica de base de datos era fácil manipular y actualizar gran cantidad de información.

Es fácil distinguir los componentes básicos de una base de datos:

Su estructura es descrita por un conjunto de nombres de campos, estos campos pueden ser de varios tipos en función del dato a almacenar (números, fechas, etc) y de longitud definible.

Otro componente son los datos propiamente dichos.

• Los gestores de bases de datos permiten la organizacíón y el tratamiento eficaz de grandes masas de datos proporcionándonos gran variedad de herramientas.

DBASE II proporciona un gestor de base de datos de tipo relacional con capacidad para gestionar las bases de datos, interpretar interactivamente instrucciones y ejecutar bloques de sentencias (programas).

DBASE II también contribuyó a la filosofía de la programación estructurada, mejoró sus prestaciones y evolucionó en varias versiones (DBASE III, DBASE III+ y DBASE IV).

591

Page 592: Programacion en Java

George Tate fallecido tempranamente nunca pudo comprobar la revolución que ocasionaría este producto, aún en constante evolución.

El éxito obtenido entre los usuarios de micros, principalmente atraídos por su versatilidad y potencia, y los grandes beneficios producidos en su comercialización, hizo que muchas empresas de software se adherieran a la idea de desarrollar nuevos productos análogos, una gama de dialectos que hoy se les agrupa con el sobrenombre de entorno xBase (Clipper, Quicksilver, Foxbase, etc).

La difusión de estos productos han desbancado a muchos lenguajes de programación, como al Cobol que aunque propicia una fácil lectura de sus fuentes, la programación resulta lenta y laboriosa.

En los ochenta, en pleno boom informático DBASE sustituye a muchos lenguajes por la potencia de sus órdenes y facilidad de uso. Por entonces, hubo que estar muy despierto a la hora de seleccionar una herramienta de trabajo con futuro.

CLIPPER es un dialecto creado como otros tantos con la intención de mejorar las prestaciones de DBASE. Su primera versión se creó en 1985 en los laboratorios de Natuncket. CLIPPER está escrito en lenguaje C y Ensamblador y se presentó como un lenguaje atrevido que ha dado muchos quebraderos de cabeza en Ashthon-Tate. En el primer contacto que se tiene con él es dificil encontrar muchas diferencias con respecto a DBASE, ya que CLIPPER es un lenguaje formado por un conjunto de comandos y funciones similares a las usadas con DBASE, incluso la mayoría con igual formato sintáctico.

Pero no tardaremos demasiado tiempo en percartarnos de las diferencias. La principal de ellas, está en que todos los programas escritos en Clipper pueden compilarse y enlazarse. El resultado obtenido es un fichero ejecutable que puede utilizarse de forma independiente al gestor de base de datos y sin necesidad de incluir módulo runtime. Esto repercute en la velocidad de ejecución de los programas.

Muchos programadores recordarán que cuando entregaban un proyecto a un cliente desarrollado en DBASE II o III se veían con la fatalidad de entregar los ficheros fuentes, ya que DBASE lo que hacía era interpretarlos. CLIPPER salvaguardó estos intereses. CLIPPER aportó más comandos y funciones y prescindió de muchos de DBASE.

CLIPPER es ahora sin duda el compilador más utilizado en aplicaciones de gestión de datos para microordenadores. La última versión aparecida en el mercado es la CLIPPER 5.01 versión reparada de la CLIPPER 5.0. Hasta el momento, la versión más utilizada quizás por su largo tiempo de vigencia es la CLIPPER SUMMER '87. Anteriores a ésta eran la CLIPPER AUTUMN '86 y la versión de 1985.

De todas la versiones detalladas la SUMMER '87 ha sido la más difundida. Muchas aplicaciones se han desarrollado con esta versión, por ello, aún, muchos programadores se resisten al cambio a versiones más actuales.

Otras prestaciones de CLIPPER SUMMER '87 a destacar son las siguientes:

• Provee un conjunto de funciones para el tratamiento de ficheros en redes de area local.• Permite manejar ficheros de bajo nivel.• Posibilita la creación de funciones de usuarios y agruparlas en librerías.• Permite el uso de arrays unidimensionales.• Proporciona un depurador avanzado.

592

Page 593: Programacion en Java

La presente guía está dividida en doce capítulos. Cada capítulo describe comandos y/o funciones de Clipper referentes a temas concretos. El primero de ellos describe aspectos técnicos iniciales que es preciso conocer de este producto.

I. Características técnicas.

1. Capacidades.

Nº. máximo de registros por base de datos, 1000.000.000Nº. máximo de caracteres por registro, RAM disponibleNº. máximo de campos por registro, RAM disponibleNº. máximo de caracteres por campo, 32 kbNº. de dígitos de precisión en operaciones de cálculo, 18Nº. máximo de caracteres en una clave de indexación, 250Nº. máximo de variables de memoria, 2048Tamaño máximo de una variable de memoria, 64 kbNº. máximo de dígitos en una variable numérica, 19Nº. máximo de tablas, 2048

2. Requerimiento hardware.

Ordenador : IBM PC, XT, AT, 386 o compatibleMemoria RAM : 256 kbDisco duro : Necesario para funcionamiento óptimoCoprocesador: Si existe se aprovecha automáticamente

3. Requerimiento software.

Sistema Operativo :DOS 2.0 o superior (monousuario)DOS 3.1 o superior (multiusuario)LAN : Bajo DOS. No requiere LAN Pack. Bloqueo manual.

4. Instalación .

La instalación de CLIPPER es muy fácil, basta con copiar el contenido de todos los disquetes a un directorio o ejecutar el fichero CLIPCOPY.BAT que se encuentra en el disco de Sistema.

5. Config.sys.

Para el funcionamiento óptimo de CLIPPER conviene incluir las siguientes líneas en el fichero de configuración CONFIG.SYS.

FILES = 20BUFFERS = 8

Si se posee DOS 3.3 o superior es posible trabajar hasta con 255 ficheros abiertos simultáneamente. Para ello se debe indicar, en lugar de FILES = 20:

FILES = 255

(Es importante ajustar el número de ficheros para aprovechar al máximo la memoria).

593

Page 594: Programacion en Java

6. Autoexec.bat.

En el fichero AUTOEXEC.BAT resulta de gran utilidad incluir una línea de PATH. Esto permitirá ejecutar el compilador desde otros directorios de trabajo.

PATH C:\CLIPPER

7. Ficheros.

A los distintos ficheros que maneja CLIPPER podremos diferenciarlos por su extensión. Si hemos utilizado anteriormente DBASE, la mayoría nos resultarán familiares.

Bases de datos (.DBF)Datos memo (.DBT)Indices (.NTX) en DBASEIII (.NDX)Etiquetas (.LBL)Informes (.FRM)Texto (.TXT)Variables de memoria (.MEM)Fuentes (.PRG)Objetos (.OBJ)Compilación (.CLP)Enlace (.LNK)Overlays (.OVL)Ejecutables (.EXE)

8. Compatibilidad con DBASE.

La posibilidad de compilar DBASE con el compilador de CLIPPER está limitada por un grupo de comandos y funciones de DBASE. A continuación se muestra una relación de estos comandos y funciones:

¦ APPEND LIST FILES SET CATALOG ¦¦ ASSIST LIST HISTORY SET COLOR ON/OFF ¦¦ BROWSE LIST STRUCTURE SET DEBUG ¦¦ CHANGE LOAD SET DOHISTORY ¦¦ CLEAR FIELDS LOGOUT SET ECHO ¦¦ CREATE LABEL MESSAGE() SET ENCRYPTION ¦¦ CREATE REPORT MODIFY COMMAND SET FIELDS ¦¦ CREATE QUERY MODIFY LABEL SET HEADING ¦¦ CREATE SCREEN MODIFY QUERY SET HELP ¦¦ CREATE VIEW MODIFY REPORT SET HISTORY ¦¦ DISPLAY FILES MODIFY SCREEN SET MEMOWIDTH ¦¦ DISPLAY MEMORY MODIFY STRUCTURE SET MENUS ¦¦ DISPLAY STATUS MODIFY VIEW SET SAFETY ¦¦ DISPLAY STRUCTUR ON ERROR SET STATUS ¦¦ DISPLAY USERS ON ESCAPE SET STEP ¦¦ EDIT ON KEY SET TALK ¦¦ ERROR() RESUME SET TITLE ¦¦ EXPORT TO RETRY SET TYPEHEAD ¦¦ HELP RETURN TO MASTER SET VIEW ¦¦ IMPORT TO SET ¦¦ INSERT SET CARRY ¦

594

Page 595: Programacion en Java

Comandos y funciones no compatibles.

Otras distinciones a considerar son las referentes a las macros (en Clipper no pueden usarse para sustituir a una palabra del sistema) y los ficheros índices (en Clipper están optimizados).

Clipper proporciona un manejador de bases de datos (DBU), un emulador del punto de petición de orden de Dbase (DOT), un generador de informes y etiquetas (RL) y un generador de ficheros índices. Todo estas opciones son semejantes a las proporcionadas por Dbase.

II. Entorno de desarrollo.

1. Entorno.

Para desarrollar con CLIPPER tendremos que disponer de las siguientes herramientas básicas:

- Un editor que genere código ASCII standard.- El compilador CLIPPER.EXE.- Las librerías CLIPPER.LIB, EXTEND.LIB, OVERLAY.LIB, etc.- Un enlazador PLINK86.EXE ,LINK.EXE ,TLINK.EXE.- Un depurador de programas DEBUG.OBJ.

2. Escritura de programas.

Los requisitos básicos a cumplir para la correcta escritura de los fuentes son:

a) Los ficheros fuentes se nombrarán especificando la extensión .PRG.

b) La longitud de una línea es de 256 caracteres.

c) Una línea sólo admitirá una instrucción.

d) Las instrucciones pueden escribirse desde la primera línea en el editor.

e) Cuando sea necesario escribir líneas de instrucciones muy largas, podemos hacerlo en líneas independiente escribiendo un punto y coma al final de la línea.

f) Puede escribirse en minúsculas o mayúsculas, indistintamente.

g) El asterisco '*' se utilizará para hacer comentarios.

h) El doble '&' se utilizará para comentar líneas con instrucciones.

3. Compilación.

La compilación es una traducción del fichero fuente (.PRG) para obtener un fichero objeto (.OBJ). Consiste en transcribir cada instrucción desde el lenguaje simbólico en que está escrito el código (CLIPPER) a código comprensible por el enlazador del sistema operativo (DOS).

El fichero del compilador que proporciona CLIPPER se llama CLIPPER.EXE.

Sintaxis:

595

Page 596: Programacion en Java

CLIPPER <Fprg> [-<opción> {-<opción>}]

<Fprg> Programa fuente que se compila-<opción> Opciones de compilación

-l El módulo objeto no almacena el nº de línea del fuente.-m Hace que las llamadas DO o SET PROCEDURE no se compi- len.-o Especificar el directorio donde se depositará el fichero objeto.-p La compilación no comienza hasta que no se pulsa una tecla.-q Suprime la visión en pantalla de los números de líneas.-s Hace que no se genere módulo objeto. Verifica sólo sintaxis.-t Especificar la unidad donde se creará el fichero temporal .$$$

Es imprescindible que haya al menos un espacio en blanco entre <Fprg> y la primera opción así como entre cada una de ellas. Es obligatorio que la opción se exprese en minúsculas.

Nuestro programa puede contener asimismo diversas llamadas DO a otros módulos .PRG o a procedimientos del mismo programa. Si no le especificamos lo contrario, CLIPPER compila de forma automática los ficheros llamados por DO.

4. Enlace.

El fin de un enlazador es el de asociar los módulos objeto obtenidos mediante el compilador con las librerías donde se contienen las traducciones máquina de cada una de las sentencias,llamadas,etc. que aparecen en el módulo objeto.

a) Enlazadores

* PLINK86 (Phoenix Tec. Clipper Summer '87)

Sintaxis:

PLINK86 FI <Fobj> {,<Fobj>} [OUTPUT <Fexe>] LIB <Flib>{,<Flib>} | [@<Flnk>]

* LINK (Microsoft)

Sintaxis:

LINK <Fobj> {<Fobj>},<Fexe>,<mapa>,<Flib> {<Flib>}

* TLINK (Borland)

Sintaxis:

TLINK <Fobj> {<Fobj>},<Fexe>,<mapa|/x>,<Flib> {<Flib>}

* RTLINK (Pocket Soft. Clipper 5)

Sintaxis:

RTLINK [FI <Fobj> [OUTPUT <Fexe>] [LIB [<Flib>] [<opciones>]] | [@<Flnk>]

596

Page 597: Programacion en Java

b) Overlay

* Ficheros de enlace .LNK

Todas las claúsulas que deban indicarse al enlazador pueden situarse en un fichero de enlace .LNK. El enlazador usa uno de estos ficheros conforme a la siguiente sintaxis:

PLINK86 @<via><Flnk>

Ejemplo_1: PRUEBA.LNK

FILE pruebaLIB clipper,extend; (';'Indica el final del fichero .LNK)

* Librerias

CLIPPER.LIBEXTEND.LIBOVERLAY.LIB

* Overlays

El mayor problema con el que nos podemos encontrar, cuando estamos realizando una aplicación en Clipper, es que ésta no nos quepa físicamente en la memoria de trabajo de nuestro ordenador.

El único modo que tenemos de solucionar este problema es proceder a lo que denominamos segmentación, programación por capas, solapas u overlays.

Cuando programamos usando esta técnica, lo que hacemos es dividir la memoria RAM en dos o más áreas de trabajo. En la primera de ellas (área principal) se carga el módulo ejecutable, y en las áreas de solape se cargan y descargan, conforme se van usando, los diferentes módulos overlay que hayamos definido.

Ejemplo_1: PRUEBA.LNK (2 áreas)

FILE prgprinLIB clipper,extendOVERLAY CODE, $CONSTANTSBEGINAREASECTION FILE modulo1SECTION FILE modulo2SECTION FILE modulo3ENDAREA

Mandatos para compilar y linkar

CLIPPER prgprin -mCLIPPER modulo1CLIPPER modulo2CLIPPER modulo3PLINK86 @prueba

597

Page 598: Programacion en Java

Ejemplo_2: PRUEBA.LNK (3 áreas)

FILE prgprinLIB clipper,extendOVERLAY CODE, $CONSTANTSBEGINAREASECTION FILE modulo1SECTION FILE modulo2ENDAREABEGINAREASECTION FILE modulo3ENDAREA

Mandatos para compilar y linkar

CLIPPER prgprin -mCLIPPER modulo1CLIPPER modulo2CLIPPER modulo3PLINK86 @prueba

(Para que Clipper produzca un fichero .EXE y tantos ficheros .OVL como módulos para overlays tengamos definidos, sólo hay que cambiar la instrucción:

SECTION FILE <Fobj> {,<Fobj>}

por

SECTION INTO <Fovl> FILE <Fobj> {,<Fobj>}

(Esto último es útil para trabajar con disquetes)

* Mandatos del enlazador PLINK86

#. Sirve para poner un comentario en un fichero de enlace.

BATCH. Por defecto, cuando PLINK86 no encuentra un fichero .OBJ o .LIB de los especificados, la operación de enlace continúa adelante.

BEGINAREA. Determina el comienzo de un área.

ENDAREA. Determina el final de un área.

DEBUG. Proporciona información adicional para ayudar a la depuración de una aplicación en el caso de overlay.

FILE. Especificar los módulos objetos separados por coma (,).

HEIGHT. Nº líneas/página del informe (MAP).

LIBRARY. Especificar las librerías que serán enlazadas con los .OBJ.

598

Page 599: Programacion en Java

LOWERCASE. Convierte en minúsculas todos los identificadores y símbolos.

MAP=<Fmap>. Especificar fichero .MAP.

NOBELL. Elimina el sonido que aparece con los mensajes del PLINK86.

OUTPUT. Especificar fichero .EXE.

SEARCH. Hace una segunda pasada por las librerías si tras terminar el enlace alguno de los símbolos ha quedado sin definir.

SECTION. Determina que los módulos objeto que se relacionan tras la palabra FILE estarán en el área de overlay abierta, pero no en un fichero independiente en disco.

SECTION INTO. Igual que el anterior, pero en un fichero en disco.

UPPERCASE. Convierte a mayúsculas todos los identificadores y símbolos.

VERBOSE. Nos da información en pantalla de lo que está haciendo PLINK86.

WIDTH. Determina el ancho en columnas del informe (MAP).

WORKFILE. Sirve para direccionar el archivo temporal que usa el enlazador.

III. Bases de datos.

1. Creación de una base de datos.

Para crear un fichero de estructura vacia se usará el mandato CREATE. Para definir los distintos campos de la futura base de datos emplearemos APPEND BLANK (para añadir un registro en blanco) y REPLACE (para almacenar el contenido).

CREATE <Fstr>

Ejemplo_1:

CREATE clientesUSE clientesAPPEND BLANKREPLACE FIELD_NAME WITH "CODIGO"REPLACE FIELD_TYPE WITH "C"REPLACE FIELD_LEN WITH 5APPEND BLANKREPLACE FIELD_NAME WITH "NOMBRE"REPLACE FIELD_TYPE WITH "C"REPLACE FIELD_LEN WITH 30CLOSERETURN

Las variables de entorno FIELD_NAME, FIELD_TYPE, FIELD_LEN y FIELD_DECIMALS tomarán el nombre de campo, el tipo de campo, la longitud de campo y las posiciones decimales, respectivamente.

599

Page 600: Programacion en Java

Una vez creada la estructura pasaremos a generar la base de datos propiamente dicha con CREATE FROM.

CREATE <Fdbf> FROM <Fstr>

Ejemplo_2:

CREATE CLIENTES FROM CLIENTES

Tanto en la utilidad DOT porporcionada por Clipper como en el entorno Dbase podemos crear bases de datos sin necesidad de escribir programas.

2. Tipos y longitud de campos.

Los distintos tipos de campos que podemos definir en una base de datos son:

C - Caracter (1-254 caracteres alfanuméricos)N - Numérico (1-19 dígitos de entero.)(0-15 dígitos decimal y dos dígitos menor que entero)D - Fecha (8 dd-mm-aa)L - Lógico (1 carácter para valores lógicos: T,F,Y,N)M - Memo (10) Almacena dirección para acceder a fichero .DBT.

3. Usar una base de datos.

Para usar una base de datos emplearemos la sentencia USE especificando el fichero de base de datos. Si existe un fichero memo asociado se abrirá, y si se indicó uno o más ficheros .NTX se activarán los índices correspondientes. También proporciona el alias adecuado.

USE <Fdbf> [INDEX <Fntx1> {,<Fntx2>}] [EXCLUSIVE] [ALIAS] <alias>

El número máximo de ficheros índices asociados es 15. EXCLUSIVE se emplea para redes y posibilita la apertura de ficheros con uso exclusivo a un usuario.

USE sin más, cierra el fichero del área activa.

Ejemplo_1:

USE CLIENTES

4. Modificar estructura.

Para modificar la estructura de una base de datos se recomienda el uso de la sentencia MODIFY STRUCTURE propia de Dbase. Posibilita renombrar, suprimir y añadir campos, así como modificar el tipo y la longitud de los mismos. Con LIST STRUCTURE de Dbase listaremos la estructura de una base de datos.

MODIFY STRUCTURE

Ejemplo_1:

600

Page 601: Programacion en Java

USE CLIENTESMODIFY STRUCTURE

Hay que tener precaución si existen registros en la base de datos ya que algunas modificaciones pueden vaciarnos el contenido de uno o más campos.

5. Añadir registros.

APPEND BLANK añade un registro vacio a nuestro fichero en uso. El puntero de la base de datos se sitúa en el registro añadido. La sentencia REPLACE nos servirá para reemplazar el contenido de los campos.

REPLACE [<ambito>] [<alias1>] <campo1> WITH <expr1> {,[<alias2>]<campo2> WITH <expr2>} [FOR <expL>][WHILE <exprL>]

Ejemplo_1: USE CLIENTES

APPEND BLANKREPLACE CODIGO WITH "00001"REPLACE NOMBRE WITH "Federico Torres"

6. Listar registros.

LIST y DISPLAY sirven para visualizar, imprimir o enviar a un fichero de texto, un registro o conjuto de registros.

LIST [OFF] [<ámbito>] [<Lcam>] [FOR <expL>] [WHILE <expL>] [TO PRINT/TO FILE <ftxt>]

DISPLAY [OFF] [<ámbito>] [<Lcam>] [FOR <expL>] [WHILE <expL>] [TO PRINT/TO FILE <ftxt>]

Ejemplo_1:

USE CLIENTESLIST

Ejemplo_2:

USE CLIENTESLIST CODIGO

Ejemplo_3:

USE CLIENTESDISPLAY FOR CODIGO > "50000" TO PRINT

7. Puntero de registro.

Clipper mantiene un puntero que indica el registro activo en cada momento. Tanto en Clipper con en Dbase podemos conocer la posición del puntero con la función RECNO(). En el ejemplo anterior

601

Page 602: Programacion en Java

al añadir el registro vacio el puntero se desplaza a la posición que ocupa este registro dentro de la base de datos. Podemos deducir que las sustituciones se efectuarán ahí.

Existen mandatos que afectan únicamente al registro activo. El puntero se puede desplazar usando la sentencias GO y SKIP en sus distintas modalidades:

GO <Reg> (ir al registo indicado)GO TOP (ir al registro número 1)GO BOTTOM (ir último registro)SKIP (ir al siguiente registro)SKIP -1 (ir al anterior)etc.

8. Editar un registro.

La edición de registros es posible realizarla con varias sentencias. No es posible usar EDIT de Dbase III. En Clipper la edición de un registro puede realizarse con un grupo de GET's, aunque existen otras sentencias más avanzada como DBEDIT, MEMOEDIT, etc.

@ <Fila>,<Col> [SAY <ExpC> [PICTURE <másc> ]] [GET <Vmen> [PICTURE <masc>] [RANGE <expN1>, <expN2>] [VALID <expL>]]

PICTURE expresa un formato para la entrada/salida de información.

RANGE sirve para validar datos numéricos entre los dos límites especificados.

VALID se emplea para expresiones genéricas de validación. <expL> será la condición de validación.

Ejemplo_1:

USE CLIENTESGO 3@ 1,1 SAY " Modifique codigo: " GET CODIGO@ 2,1 SAY " Modifique nombre: " GET NOMBREREAD

Ejemplo_2:

USE ALUMNOSGO TOP@ 1,1 SAY NOMBRE@ 2,1 SAY " Modifique edad: " GET EDAD RANGE 1,7@ 3,1 SAY " Modifique sexo: " GET SEXO PICTURE "!"; VALID(SEXO$"VH")READ

READ lee las variables GET's

9. Marcar un registro.

Clipper igual que Dbase permite marcar registros para posteriormente, si procede, borrarlos definitivamente. Esto se hará con la sentencia DELETE que marca con un asterisco el registro activo. Puede marcarse más de un registro usando la claúsulas FOR o WHILE.

602

Page 603: Programacion en Java

DELETE [ámbito] [FOR <ExpL>] [WHILE <ExpL>]

[ámbito] RECORD <núm> Marcar el registro especificado.ALL Marcar todos los registros

Ejemplo_1:

USE CLIENTESGO 1DELETE

Ejemplo_2:

USE CLIENTESDELETE ALL

Ejemplo_3:

USE CLIENTESDELETE RECORD 10

Ejemplo_4:

USE CLIENTESDELETE FOR NOMBRE = "María"

10. Borrar registros.

Una vez marcado un registro es posible borrarlo con PACK

PACK

Ejemplo_1:

USE CLIENTESDELETE RECORD 10PACK

11. Desmarcar registros.

La sentencia RECALL suprime las marcas puestas con DELETE

RECALL [<ámbito>] [FOR <ExpL>] [WHILE <ExpL>]

Ejemplo_1:

USE CLIENTESRECALL RECORD 10

12. Borrar todos los registros.

ZAP borra todos los registros marcados o no de una base de datos manteniendo su estructura.

603

Page 604: Programacion en Java

13. Localizar registros.

LOCATE permite localizar uno o más registros. En el momento que encuentra un registro el puntero de registro se coloca en él, esperando a un CONTINUE para continuar con la búsqueda. La búsqueda es secuencial por lo que si el tamaño de la base de datos es considerable puede resultar lento este proceso.

LOCATE [<ámbito>] [FOR <expL>] [WHILE <expL>]CONTINUE

Ejemplo_1:

USE CLIENTESLOCATE FOR CODIGO > "10000" .AND. NOMBRE = "JOSE"

14. Operaciones con bases de datos.

En una base de datos es posible contar registros, y realizar operaciones de suma y media aritmética. COUNT nos servirá para contar, SUM para sumar el contenido de campos numéricos y AVERAGE para calcular la media aritmética.

COUNT [<ámbito>] [FOR <expL>] [WHILE <expL>] TO <vmen>

COUNT cuenta el número de registros que cumplen una determinada condición especificada. Dicha información ha de depositarse obligatoriamente en una variable numérica de memoria.

<ámbito> es por defecto ALL

SUM [<ámbito>] TO <vmen>

SUM suma uno o más campos depositando el resultado en una variable.

AVERAGE [<ámbito>} TO <vmen>

AVERAGE calcula la media aritmética de uno o más campos.

15. Exportar.

COPY TO Copia toda la base de datos en curso o sólo una parte a un nuevo archivo.

COPY TO <archivo> [<ámbito> [FIELDS <lista campos>][FOR <condición>] [WHILE <condición>] [SDF/DELIMITED/DELIMITED WITH <delimitador>]

<archivo> - Es el nombre del nuevo archivo.

<ámbito> - Determina la porción del archivo a copiar, por defecto es ALL (todo).

FIELDS <lista campos> - Son los campos a copiar a la nueva base de datos.

FOR/WHILE <condición> - Especifican la condición a cumplir.

604

Page 605: Programacion en Java

SDF - Especifica que el archivo de salida será con formato ASCII, con campos de longitud fija.

DELIMITED - Formato para el archivo de salida ASCII, con campos de longitud variable y separados por comas. Si se desea pueden separase con espacios (BLANK), o con cualquier otro delimitador.

Ejemplo_1:

USE HELPCOPY TO HELP.TXT SDF

16. Importar.

APPEND FROM añade datos a la base en uso a partir de otro archivo que puede ser que no sea (.DBF). Podemos seleccionar loa datos a añadir mediante cualificadores.

APPEND [<registros> [FIELDS <campos>] FROM <fichero>[FOR <condición>] [WHILE <condición>] [SDF/DELIMITED[WITH BLANK/<delimitador>]]

<registros> - Registros a agregar por defecto son todos.

<campos> - Lista de campos a agregar.

<fichero> - Nombre del archivo origen. Por defecto, (.DBF),

FOR/WHILE <condición> - Indican las condiciones que han de cumplir los registros para ser agregados.

SDF - Identifca archivos ASCII.

DELIMITED - Archivos ASCII con separación de campos con comas.

DELIMITED WITH BLANK - Campos separados por un espacio

DELIMITED WITH <delimitador> - Podemos especificarlo.

Ejemplo_1:

USE CLIENTESAPPEND FROM VENTAS FOR PEDIDO > 5000

IV. Indices.

1. Crear ficheros índices.

INDEX indexa un fichero de datos por el campo que le indiquemos. Crea en disco un fichero con la extensión .NTX. Pueden usarse también claves múltiples formada por la suma de varios campos, de partes de campos, expresiones y campos, etc, pero recuerde que el máximo número de caracteres de una clave será de 250. Para sumar campos hemos de tener siempre la precaución de convertirlos previamente a cadena. Los ficheros índices no son compatibles con los de Dbase III. Cuando un índice está abierto con su correspondiente base de datos se actualiza de forma

605

Page 606: Programacion en Java

automática. Una base de datos puede tener asociados como máximo 15 ficheros índices. Los registros que se encuentran marcados para ser borrados también forman parte del índice.

INDEX ON <campo1> {+<campo2>} TO <Fntx>

Ejemplo_1:

USE CLIENTEINDEX ON NOMBRE TO NOMCLI

Ejemplo_2:

USE CLIENTEINDEX ON NOMBRE+DTOS(FECHA) TO FECCLI

2. Activar fichero índice.

Como vimos anteriormente en el capítulo I, la activación de índices se realiza con USE. Se pueden especificar uno o más ficheros índices. Con SET ORDER TO se establecerá el índice activo. Esta sentencia altera el ordenamiento de la declaración inicial de índices hecha con USE...INDEX. Si indicamos SET ORDER TO 0 se desactivan todos los ficheros índices. No obstante, la importancia de este mandato estriba en que no tenemos necesidad de abrirlos de nuevo para activarlos.

SER ORDER TO <expN>

<expN> es el número de índice activo. Puede valer de 0 a 15.

Ejemplo_1:

NOMBRE = SPACE(20)FECHA = CTOD(SPACE(8))USE CLIENTES INDEX NOMCLI,FECCLI,DOMCLISET ORDER TO 2LIST NOMBRE,FECHA TO PRINT

3. Búsqueda por índice.

SEEK busca una expresión en una clave índice.

SEEK <expr>

Ejemplo_1:

USE CLIENTES INDEX NOMCLISEEK "LUIS MARIN"IF FOUND()@ 4,4 SAY FECHA@ 5,4 SAY VENTASELSE@ 10,1 SAY "No existe CLIENTE"ENDIF

4. Area de trabajo.

606

Page 607: Programacion en Java

SELECT selecciona las diferentes áreas de trabajo en que vamos a situar nuestros ficheros de datos. El último SELECT que enunciemos es aquel que contendrá el fichero activo.

SELECT <área>/<alias>

<área> es un número comprendido entre 0 y 254.

<alias> es el nombre de un área de trabajo existente si hay un fichero abierto en ese área. Se puede hacer referencia a las 10 primeras áreas de trabajo con las letras A a J.

En Clipper se pueden utilizar 255 áreas de trabajo. En cada área de trabajo se pueden abrir un fichero de base de datos y 15 ficheros índices como máximo asociados a él.

Ejemplo_1:

SELECT 1USE CLIENTESSELECT 2USE DIARIOVTAS

Ejemplo_2:

SELECT 1USE CLIENTES INDEX NOMCLI ALIAS CLISELECT 2USE DIARIOVTAS INDEX TOTALVTAS ALIAS DIA......SELECT CLISEEK "LUIS PEREZ"IF FOUND()CODCLI = CODIGOSELECT DIASEEK CODCLIIF FOUND()@ 10,10 SAY PTASVENTASENDIFENDIF

5. Cierre de ficheros.

CLOSE cierra el fichero de base de datos abierto en el área activa así como sus índices asociados.

CLOSE DATABASES cierra todos los ficheros de todas las áreas de trabajo, así como sus correspondientes índices.

CLOSE INDEX cierra todos los índices del área de trabajo activa.

CLOSE ALL cierra todos los ficheros abiertos.

V. Variables de memoria.

1. Tipos de variables.

607

Page 608: Programacion en Java

Variable es un nombre asignado a una posición de memoria que se puede utilizar para almacenar un dato concreto. Los tipos de variables por el tipo de dato que contienen son:

-numéricas-alfanuméricas-lógicas-fechas

2. Nombrar una variable de memoria.

Independientemente del tipo a que pertenezca una variable, debe asignársele un nombre, que puede ser de uno a diez caracteres pueden ser una combinación de letras, dígitos o signo de subrayado. El primer carácter de una variable de memoria debe ser una letra. Los siguientes nombres son nombres de variables de memoria permitidos.

COMPRASPrecioI_V_AMES_1_A_6

No debe utilizarse el mismo nombre para una variable y para un campo en la misma aplicación.

3. Introducción de datos en una variable.

Las instrucciones STORE y el signo igual (=) pueden emplearse indistintamente para la asignación de datos a variables de memoria.

STORE <dato> TO <var><var> = <dato>

Ejemplo_1:

PTAS = 0FECHA_ALTA = CTOD(SPACE(8))STORE "enero" TO MES

4. Visualización de variables.

Para visualizar el contenido de una variable puede usarse la interrogación (?) con los siguientes formatos:

? <expr>?? <expr>

Ejemplo_1:

? MES?? "HOLA"

5. Expresiones.

608

Page 609: Programacion en Java

Además de servir como depósito temporal, las variables de memoria pueden utilizarse en procesosde operaciones. Una variable de memoria puede ser incluida en una expresión para definir un procedimiento, para describir una condición en una instrucción o para servir como elemento de salida (resultado de una operación).

Pueden utilizarse diferentes tipos de expresión. Una expresión puede incluir un campo de datos, una variable de memoria, una constante o una combinación de todo ello. Sin embargo, todos los elementos de una expresión deben ser del mismo tipo.

La expresión más corriente es la expresión aritmética, que puede contener un valor, una variable de memoria, un campo numérico y una combinación de éstos unidos por uno o más operadores aritméticos. Las expresiones son útiles para realizar cálculos matemáticos. Puede utilizarse una expresión para asignar un valor a una variable de memoria o para reemplazar el contenido de un campo numérico con un nuevo valor.

Cuando se incluye más de un operador aritmético en una expresión, ésta se valora de izquierda a derecha de acuerdo con siguiente sistema de prioridades:

Prioridad máxima : ** ^Prioridad secundaria: * /Baja prioridad : + -

Se pueden utilizar paréntesis en una expresión para definir la secuencia de evaluación y suprimir el sistema normal de prioridades. El material dentro de los paréntesis siempre es evaluado previamente. Cuando haya paréntesis anidados es una expresión aritmética, la expresión del paréntesis interno es evaluado en primer lugar, luego se evalúa el paréntesis externo. Dentro de un paréntesis, los operadores se evalúan según el sistema de prioridades, de izquierda a derecha.

6. Declaración pública y privada.

PUBLIC declara variables de memoria como globales o públicas. Estas pueden modificar su valor en cualquier parte del programa.

PUBLIC <Lvar>

PRIVATE declara de uso privado la variables de memoria especificadas. Estás pueden modificar su valor en partes de un programa.

PRIVATE <Lvar>

7. Salvar y restaurar variables de memoria.

SAVE TO salva en un fichero variables de memoria.

SAVE TO <fmem> [ALL [LIKE <masc>/EXCEPT <masc>]]

<fmem> es el nombre del fichero donde se almacenarán las variables. Si no se especifica la extensión por defecto es .MEM.

ALL salva en el fichero todas las variables existentes.

LIKE <masc> salva en el fichero todas las variables cuya estructura sea semejante a la especificada en <masc>. Recuerde que puede hacer uso de los símbolos comodines: * y ?.

609

Page 610: Programacion en Java

EXCEPT <masc> salva todas las variables que no tengan una estructura semejante a <masc>.

RESTORE FROM restaura desde disco el fichero de variables de memoria <Fmem>. Si se usa ADDITIVE no se borra el entorno de variables activo al restaurar.

RESTORE FROM <Fmem> [ADDITIVE]

Al restaurar las variables de memoria, éstas son privadas, a no ser que se especifiquen como públicas antes de restaurarlas y se utilice la claúsula ADDITIVE.

Ejemplo_1:

conf_cla = "1234"conf_dis = "A"conf_dir = "C:\GESTION\"conf_mar = 20conf_col = "S"SAVE TO CONFIG ALL LIKE conf_*

Ejemplo_2:

RESTORE FROM CONFIG ADDITIVEclave = SPACE(4)@ 1,1 SAY "Teclear Clave: " GET claveREADIF clave = conf_cla......ENDIF

8. Eliminar variables de memoria.

RELEASE elemina de la vemoria las variables especificadas.

RELEASE [<vmem1> {,<vmem2>}] [ALL [LIKE<masc>] EXCEPT <masc>]]

<vmem1>..<vmem2> es la lista de variables que se desean eliminar.

ALL indica que sean eliminadas todas las variables existentes.

ALL LIKE <masc> indica que sean eliminadas todas las variables cuya estructura sea semejante a la expresada en <masc>. Se pueden usar los comodines: * y ?.

ALL EXCEPT <masc> indica que sean borradas todas las variables que no concuerden con la estructura expresada en <masc>.

Ejemplo_1:

conf_cla = "1234"conf_dis = "A"conf_dir = "C:\GESTION\"conf_mar = 20

610

Page 611: Programacion en Java

conf_col = "S"RELEASE ALL

9. Macros.

Las macros sirven en CLIPPER para forzar la sustitución de una variable por su valor en aquellos puntos de programa donde por si misma la variable no se traduciría. Cuando tras una macro se sigue algún tipo de expresión hemos de indicar al sistema que la macro termina con un punto (.).

&<vmem>

Ejemplo_1:

nombre = "lápiz"? "Artículo: &nombre"

Ejemplo_2:

base = "CLIENTES"USE &base

Ejemplo_3:

filtro = "EDAD > 18 .AND. SEXO = 'V'"USE CLIENTESLIST NOMBRE FOR &filtro

10. Operadores y valores lógicos.

a) Operadores lógicos.

.AND. (Y además)

.OR. (O además)

.NOT. (Negación)! (Negación)

Ejemplo_1:

IF EDAD > 18 .AND. EDAD < 65PAGAR = 10000ENDIF

Ejemplo_2:

IF !FILE("CLIENTES.DBF")@ 1,1 SAY " Error no encuentra base de datos "ENDIF

b) Valores lógicos. Representan pares de valores.

.T. (Verdadero)

.F. (Valso)

611

Page 612: Programacion en Java

.Y. (Si)

.N. (No)

Ejemplo_1:

JUBILADO = .Y.IF JUBILADO..ENDIF

11. Operadores relacionales.

= (Igual que)== (Exactamente igual que)> (Mayor que)< (Menor que)>= (Mayor igual que)<= (Menor igual que)<> # (Distinto)

Ejemplo_1:

DO CASECASE MES = 1........CASE MES >= 2........ENDIF

VI. Operaciones de entrada y salida.

1. Entrada, máscara, validación y rango.

@...SAY/GET muestra en las coordenadas reseñadas el contenido de la expresión que sigue a SAY, carga valores a los campos o las variables de memoria que siguen a GET (hasta ser leídos por READ.)

Las variables usadas han de ser declaradas previamente.

@ <fila>.<col> [SAY <expC> [PICTURE <masc>]][GET <expC> [PICTURE <masc>][RANGE <expN1>,<expN2>][VALID <expL>]]

PICTURE expresa un formato para la entrada/salida de información. Este formato puede estar controlado por plantillas o funciones. Las primeras se aplican carácter a carácter y las segundas afectan a toda la claúsula. Las funciones irán precedidas del símbolo @.

RANGE sirve para validar datos numéricos, indicando un límite inferior y un superior. Entre estos límites deberá estar comprendido en dato numérico para que sea válido.

612

Page 613: Programacion en Java

VALID se emplea para expresiones genéricas de validación. <expL> será la condición de validación.

Ejemplo_1:

nombre = SPACE(20)@ 2,1 SAY "Teclear nombre: " GET nombre PICTURE "@!"READ

Ejemplo_2:

edad = 0@ 3,3 SAY "Teclear edad: " GET edad PICTURE "999" RANGE 19,125READ

Ejemplo_3:

resp = SPACE(1)@ 5,5 say "¿ GRABAR ? " GET resp "!" VALID(resp$"SN")READ

Ejemplo_4:

importe = 0@ 5,5 SAY " TECLEAR IMPORTE: " GET importe PICTURE "@E9,999.99"READ

Símbolos usados por PICTURE

A Hace que un GET sólo admita letrasL Idem sólo para valores lógicos.Y Permite sólo "Y" o "N"N Idem sólo letras y caracteresX Idem cualquier carácter9 Permite que sólo se visualicen dígitos# Idem sólo letras, espacios y signos! Idem sólo letras mayúsculas, Representa los miles en los datos numéricos$ Hace que se muestren $ para rellenar una cifra por la izquierda.* Idem con *

Símbolos utilizados como funciones <"@<símbolo>")

C Indica CR después de un número positivoX Indica DB después de un número negativo( Encierra con paréntesis números negativos con espacios a la izquierda.) Idem sin espacios a la izquierdaB Justifica los números por la izquierdaA Hacen que sólo se puedan captar caracteres alfabéticos! Hace que sólo se permitan letras mayúsculasR Permite insertar caracteres que aparecerán solamente en pantalla, no almacenándose en la variableE Convierte los números al formato europeoD Visualiza las fechas en el formato especificado con SET DATE

613

Page 614: Programacion en Java

K Borra el contenido de la variable si no se pulsa primero un carácter de control del cursorS Hace scroll horizontal con la variableZ Hace que los valores cero en un campo numérico se representen como blancos.

ACCEPT acepta datos alfanuméricos por pantalla y los carga en <mven>. No es necesario haber declarado previamente <Vmen>.

ACCEPT [<expC>] TO <vmen>

Ejemplo_1:

ACCEPT "Escribe tu nombre" TO nombre

INPUT Acepta datos por pantalla. Los datos han de ser identificados con sus correspondientes indicadores, así, por ejemplo, una cadena de carácteres deberá escribirse entrecomillada, mientras que esto no será preciso con un número.

INPUT [<expC>] TO <vmen>

Ejemplo_1:

INPUT " Edad " TO edad

WAIT detiene la ejecución del programa y espera la pulsación de una tecla.

WAIT [<expC>] [TO <vmen>]

<expC> es una cadena de caracteres que se visualizarán a modo de información. Si se omite, aparecerá en pantalla: Press any key to continue...

<vmen> es una variable que contendrá el carácter qu se ha pulsado

2. Pausa.

INKEY() detiene por un tiempo el flujo del programa y devuelve el valor de la tecla que se está pulsando.

INKEY([<expN>])

<expN> indica el número de segundos de espera. Si es igual a cero detiene el programa y espera que pulsemos una tecla cuyo valor ASCII toma.

Ejemplo_1:

tecla = INKEY(0)

Pulsando [enter],

tecla = 13

3. Conocer la última tecla pulsada.

614

Page 615: Programacion en Java

LASTKEY() devuelve el valor de la última tecla pulsada. Dicho valor es un número que se corresponde con el valor ASCII del carácter.

Ejemplo_1:

INKEY(0)DO CASECASE LASTKEY() = 27RETURNCASE CHR(LASTKEY()) = "+".....CASE LASTKEY() = 13.....ENDCASE

4. Salida.

?, ??, @ SAY, TEXT/ENDTEXT se emplean generalmente como instrucciones de salida (pantalla/impresora) para expresiones, cadenas, bloques de texto, etc.

Ejemplo_1:

TEXT*********************ERROR*********************ENDTEXT

Ejemplo_2:

a=4b=5c=3? (a*b)**c

5. Borrar pantalla.

CLEAR borra la pantalla, manteniendo los atributos de color vigente, y libera todos los GET pendientes. Asimismo, posiciona el cursor en la posición 0,0 (posiciones verticales 0 a 24 / posiciones horizontales 0 a 79).

@..CLEAR TO borra un área de pantalla.

Ejemplo_1:

@ 3,3 CLEAR TO 9,9

6. Dibujar un marco.

@..TO dibuja un marco de línea sencilla en las coordenadas especificadas. Si se emplea la opción DOUBLE, el marco dibujado será de línea doble.

@ <fila sup>,<col izq> TO <fila inf>,<col dcha> [DOUBLE]

615

Page 616: Programacion en Java

7. Dibujar una caja.

@..BOX construye una caja entre las coordenadas indicadas y con los códigos ASCII especificados en <expC>. El orden de los caracteres es:

1. Esquina superior izquierda2. Línea horizontal superior3. Esquina superior derecha4. Línea vertical derecha5. Esquina inferior derecha6. Línea horizontal inferior7. Esquina inferior izquierda8. Línea vertical izquierda9. Carácter de relleno

@ <fila sup>,<col izq>,<fila inf>,<col dcha> BOX <expC>

Ejemplo_1:

cadena = "+-+¦+-+¦¦"@ 1,1,10,10 BOX cadena

8. Hacer un menú.

@..PROMPT facilita la creación de menús en nuestros programas. Cada opción se muestra con un PROMPT en una posición especifica de la pantalla y se le acompaña opcionalmente de un mensaje aclaratorio.

@ <fila>,<col> PROMPT <expC> [MESSAGE <expC>]

SET WRAP ON/OFF posibilita la rotación al alcanzar la primera o última opción.

SET MESSAGE determina el número de fila donde aparecerán los mensajes de las distintas opciones.

SET MESSAGE TO [<fila> [CENTER/CENTRE]]

CENTER/CENTRE muestra el mensaje en la fila especificada centrándolo.

MENU TO sirve para leer el valor numérico que representa a la opción seleccionada. Dicho valor se asigna automáticamente y representa el número de orden de cada PROMPT.

Ejemplo_1:

SET WRAP ONSET MESSAGE TO 23 CENTER@ 1,1 PROMPT "ALTA " MESSAGE "Alta de usuarios "@ 2,1 PROMPT "BAJA " MESSAGE "Baja de usuarios "@ 3,1 PROMPT "LISTADO " MESSAGE "Listado DESDE/HASTA"MENU TO opcionDO CASECASE opcion = 1.....

616

Page 617: Programacion en Java

CASE opcion = 2.....CASE opcion = 3.....ENDCASE

9. Salvar/Restaurar pantallas.

SAVE SCREEN salva la pantalla actual así como su estructura de variables leídas y pendientes de leer.

SAVE SCREEN [TO <vmem>]

TO <vmem> indica que la pantalla será almacenada en la variable de memoria <vmem>. Esta variable será de tipo carácter.

RESTORE SCREEN restaura una pantalla almacenada previamente

RESTORE SCREEN [FROM <vmem>]

SAVESCREEN() almacena una parte de la pantalla en una variable de memoria

<vmen> = SAVESCREEN(<fil sup>,<col izq>,<fil inf>,<col dcha>)

RESTSCREEN() restaura una área de una pantalla salvada previamente.

RESTSCREEN(<fil sup>,<col izq>,<fil inf>,<col dcha>,<vmem>)

Ejemplo_1:

@ 2,3 SAY " -------- "@ 3,3 SAY " CLIENTES "@ 4,3 SAY " -------- "SAVE SCREEN TO pantaCLEARINKEY(0)RESTORE FROM pantaRETURN

Ejemplo_2:

@ 2,3 SAY " -------- "@ 3,3 SAY " CLIENTES "@ 4,3 SAY " -------- "panta = SAVESCREEN(3,3,4,12)CLEARINKEY(0)RESTSCREEN(5,5,6,14,panta)

VII. Bifurcación y bucles.

1. IF (Si cumple condición...).

617

Page 618: Programacion en Java

Bifurca un programa entre una condición y su opuesta. Puede usarse como mandato o como función. En el primer caso, lo que hace es ejecutar alternativamente unas instrucciones u otras y en el segundo devolver alternativamente un valor u otro. La sintaxis de la función puede ser IF() o IIF().

Mandato:

IF <expL><instrucciones>[ELSEIF <expL>]<instrucciones>[ELSE<instrucciones>]ENDIF

Función:

IIF/IF(<expL>,<.T.>,<.F.>)

<expL> es la condición que se desea establecer

ELSEIF reconoce órdenes cuando se cumple la que condición expresada.

ELSE realiza las distintas órdenes que se indican cuando la condición es falsa.

<.T.> Indica la expresión a evaluar para el valor verdadero de la condición.

<.F.> Indica la expresión a evaluar para el valor falso de la condición.

Ejemplo_1:

IF sexo = "V"peso = 20ELSEpeso = 12ENDIF

Ejemplo_2:

IF porcentaje > 10porcentaje = porcentaje - 2ENDIF

Ejemplo_3:

salario = salario + IIF(ho>80,80000+1500*(ho-80),80000)

2. DO CASE (En caso de cumplir condición...).

Bifurca la ejecución de un programa según las diferentes condiciones especificadas. OTHERWISE representa todos los casos que no cumplen ninguna condición.

618

Page 619: Programacion en Java

DO CASECASE <expL><instrucciones>CASE <expL><instrucciones>OTHERWISE<instrucciones>ENDCASE

<expL> son las diferentes condiciones.

Ejemplo_1:

DO CASECASE velocidad > 180consumo = 4CASE velocidad > 120consumo = 3CASE velocidad > 80consumo = 2OTHERWISEconsumo = 1ENDCASE

3. FOR..NEXT (Desde un valor hasta alcanzar otro).

Permite la creación de una estructura de bucle que se ejecuta para un rango de valores determinados de forma ascendente o descendente.

FOR <expN1> TO <expN2> [STEP <expN3>]<instrucciones>[EXIT]<instrucciones>[LOOP]NEXT

<expN1> es el valor inicial. Este valor se asignará a una variable de control.

<expN2> es el valor final del bucle.

STEP <expN3> indica el incremento o decremento de la variable. Por defecto incrementa en 1.

EXIT detiene el bucle pasando el control a la sentencia posterior a NEXT.

LOOP pasa de nuevo el control al comienzo del bucle, sin necesidad de que se llegue a NEXT.

Ejemplo_1:

FOR N=1 TO 10CUADRADO = N**N? CUADRADONEXT

619

Page 620: Programacion en Java

4. DO WHILE (Hacer mientras que cumpla condición...).

DO WHILE realiza una estructura de bucle mientras se cumpla la condición especificada. DO WHILE comienza y continúa el bucle si se cumple la condición. ENDDO devuelve el control al principio.

DO WHILE <expL><instrucciones>[EXIT][LOOP]ENDDO

<expL> es la condición que se debe cumplir para que se ejecute el bucle.

LOOP manda todo el proceso de nuevo al comienzo del bucle, sin necesidad de que se llegue al final, es decir a ENDDO.

EXIT fuerza a que se pare el proceso y sale del bucle aunque la condición no haya cesado de darse.

Ejemplo_1:

DO WHILE .T.@ 2,2 PROMPT "CLIENTES"@ 3,2 PROMPT "PROVEEDORES"MENU TO opcionDO CASECASE opcion = 1DO CLICASE opcion = 2DO PROCASE LASTKEY() = 27CLEARRETURNENDCASEENDDO

Ejemplo_2:

C=0DO WHILE C<100C=C+1? CENDDO

VIII. Fin.

1. Retornar.

RETURN termina un procedimiento, programa, o función, devolviendo el control al procedimiento de llamada o al DOS.

Ejemplo_1:

620

Page 621: Programacion en Java

DO BORRAR WITH 2,2,20,20......QUIT

PROCEDURE BORRARPARAMETERS X1,Y1,X2,Y2@ X1,Y1 CLEAR TO X2,Y2RETURN

2. Terminar.

QUIT termina la ejecución de un programa devolviendo el control al DOS.

Este mandato realiza la misma función que CANCEL o que RETURN en el procedimiento de más alto nivel.

Ejemplo_1:

USE FICHERO INDEX INDICESEEK CLAVEIF FOUND()DO PROCESOELSEQUITENDIF

3. Cancelar.

CANCEL cancela la ejecución de un programa o procedimiento, devolviendo el control al sistema operativo.

Ejemplo_1:

clave = SPACE(4)@ 4,4 SAY "Clave: " GET clave PICTURE "@!"READIF clave # "9876"CANCELELSEENDIF

IX. Mantenimiento de ficheros.

1. Renombrar fichero.

RENAME renombra ficheros. Es el equivalente al RENAME del DOS ,aunque su sintaxis es algo distinta.

RENAME <fich1> TO <fich2>

621

Page 622: Programacion en Java

<fich1> es el nombre inicial del fichero y <fich2> es el nuevo nuevo. Tanto <fich1> como <fich2> deben incluir la extesión del fichero.

Ejemplo_1:

RENAME CLIENTES.DBF TO CLIENTES.DATUSE CLIENTES.DAT

2. Copiar ficheros.

COPY FILE copia el contenido de <fich1> en <fich2>. No sirven con este mandato los comodines para copiar varios ficheros en bloque. Salvo esta excepción funciona igual que el COPY del DOS.

Es importante recordar que siempre hemos de proporcionarle las vías donde buscar los ficheros a copiar y donde queremos copiarlos. Si no se le especifica esta última el fichero se deposita en el directorio de trabajo.

COPY FILE <fich1> TO <fich2>

<fich1> es el fichero origen y <fich2> el fichero destino.

Ejemplo_1:

COPY FILE CLIENTES.DAT TO CLIENTES.DBF

3. Borrar ficheros.

DELETE FILE y ERASE borran ficheros. Al especificar el nombre del fichero a borrar debe figurar también su extensión. Antes de usar este comando es necesario cerrar el fichero a borrar con el comando CLOSE.

ERASE/DELETE FILE <fich>

Ejemplo_1:

USE CLIENTES....CLOSE DATABASESDELETE FILE CLIENTES.DBF

4. LLamada al Dos.

Además de las órdenes elementales de mantenimiento de ficheros vistas anteriormente, existe la posibilidad de invocar cualquiera del DOS con RUN o !. Por ejemplo, para salir temporalmente de un programa podemos incluir un RUN COMMAND.COM y regresar con EXIT.

RUN <sent>! <sent>

Ejemplo_1:

622

Page 623: Programacion en Java

RUN CHKDSK > CHEQDIS.TXT

5. Comprobar la existencia de un fichero.

Antes de realizar cualquier operación con un fichero podemos comprobar su existencia con la función FILE() que nos retornará un verdadero (.T.) o un falso (.F.).

FILE(<fich>)

Ejemplo_1:

IF FILE("CLIENTES.DBF")SORT ON NOMBRE TO CLISORTDELETE FILE CLIENTES.DBFENDIF

X. Procedimientos y funciones.

1. Procedimiento.

PROCEDURE indica el principio de un procedimiento.

PROCEDURE <nombre procedimiento><órdenes>[RETURN]

<nombre procedimiento> - Debe de empezar con una letra y sólo evalúa los 10 primeros caracteres.

RETURN - Es aconsejable su uso para determinar el fin de un procedimiento, aunque no necesario, ya que detecta el fin al encontrar otro procedure o una marca de fin de archivo.

Ejemplo_1:

CLEARDO FONDOINKEY(0)RETURN

PROCEDURE FondoFOR I=0 TO 24@ I, 0 SAY REPLICATE("¦", 80 )NEXTRETURN

2. LLamada a un procedimiento.

DO ejecuta un procedimiento escrito en Clipper, C o ensamblador, pasándole parámetros (hasta 128) con WITH.

DO <procedimiento> [WITH <lista de parmetros>]

623

Page 624: Programacion en Java

Ejemplo_1:

PROCEDURE LISTACURSOSIF !ISPRINTER()DO MSGIMPRESORAENDIF* órdenes ...RETURN

PROCEDURE MSGIMPRESORACLEAR@ 9, 28 TO 12, 51@ 10,30 SAY "CONECTE LA IMPRESORA"@ 11,32 SAY "Y PULSE UNA TECLA"INKEY(0)RETURN

3. Creación de un fichero de procedimientos.

SET PROCEDURE Activa los archivos de procedimientos especificados.

SET PROCEDURE TO [ <archivo> ]

<archivo> - Si se omite la extensión, se asume que es (.PRG).

Ejemplo_1:

SET PROCEDURE TO CLIENTESSET PROCEDURE TO PROVEEDSET PROCEDURE TO MATERIAL

4. Nombre del procedimiento y número de línea.

PROCNAME() indica el nombre del procedimiento o programa que estamos ejecutando.

PROCNAME()

Ejemplo_1:

? "Procedimiento en uso : ", procname()

PROCLINE() Devuelve el número de la línea del código fuente en curso del programa. Siempre que no le hayamos indicado al compilador que no numere las líneas.

PROCLINE()

Ejemplo_1:

? procline(), "Linea ", cLinea

5. Creación de una función.

624

Page 625: Programacion en Java

FUNCTION Declara una función definida por el usuario escrita en Clipper.

FUNCTION <nombre función><instrucciones>RETURN <valor de respuesta>

<nombre de función> - Solo acepta los diez primeros caracteres.

<valor de respuesta>- Es obligatorio la devolución de un valor.

Para llamar a una función de usuario, proceda del siguiente modo:

función( <lista de parámetros> )

Los parámetros se pasan por valor, exceptuando los arrays, o si el parámetros es precedido por una arroba (@), entonces es pasado por referencia.

Ejemplo_1:

CLEAR@ 24,0 SAY ISBISIESTO( DATE() )RETURN

FUNCTION ISBISIESTOPARAMETERS DFECHAPRIVATE DANY, CCADENA, LDEVUELVEDANY = YEAR( DFECHA )CCADENA = CTOD( "29-02-" + STR(DANY))IF DOW(CCADENA)=0LDEVUELVE = .F.ELSELDEVUELVE = .T.ENDIFRETURN LDEVUELVE

6. Conocer el número de parámetros.

PCOUNT() Determina el número de parámetros pasados a un procedimiento o función definida por el usuario.

PCOUNT()

Ejemplo_1:

PROCEDURE EDITORPARAMETERS CFICHEROIF PCOUNT() = 0@ 24,0 SAY "INDIQUE EL FICHERO: " GET CFICHEROREADENDIF

XI. Tablas.

625

Page 626: Programacion en Java

1 Declaración tablas.

Una tabla es un área de memoria que puede reservarse para contener un grupo de datos. Una tabla consta de un identificativo o nombre y un número definible de posiciones (de 1 a 1024 en Clipper '87). Estas posiciones pueden contener datos numéricos, alfabéticos, fechas, etc. Para acceder a uno de los datos contenido en una tabla se hará indicando el número de posición que ocupa. Existen varias funciones que posibilitan realizar operaciones en una tabla tales como añadir nuevos datos, eliminar datos, rellenar, etc. Este tipo de estructuras de memoria se utilizan como soporte temporal de los datos.

DECLARE declara una o más áreas de memoria (arrays) con una longitud específica. Antes de poder realizar cualquier operación con una tabla debemos declararla.

DECLARE <array>[<expN>]{,<array>[<expN>]...}

<array> es el nombre de la tabla<expN> es la longitud de la tabla (1-1024)

Ejemplo_1:

DECLARE PROVINCIA[8]PROVINCIA[1] = "ALMERIA"PROVINCIA[2] = "CADIZ "PROVINCIA[3] = "CORDOBA"....

Ejemplo_2:

numero = 8DECLARE PROVINCIA[numero]Ejemplo_3: tipo = "FICHA"numero = "01"tabla = tipo+numeroDECLARE &tabla[4]&tabla[1] = "ANDALUCIA"&tabla[2] = 8&tabla[3] = .T.&tabla[4] = CTOD("01/01/92")

2 Longitud.

LEN es una función que devuelve el número de elementos que tiene una tabla, o lo que es lo mismo la longitud de la tabla indicada.LEN(<array>)

<array> - Nombre de la tabla

Ejemplo_1:

DECLARE PROVINCIA[8]? LEN(PROVINCIA)

626

Page 627: Programacion en Java

3 Insertar.

La inserción de nuevos elementos en una tabla es posible mediante la función AINS indicándose el nombre de la tabla y la posición donde se desea insertar el nuevo elemento. Automáticamente, el elemento insertado desplazará a los posteriores en una posición y el último se perderá.

AINS(<array>,<expN>)

<array> - Nombre de la tabla<expN> - Posición elemento

Ejemplo_1:

DECLARE NOMBRE[3]NOMBRE[1] = "LUIS"NOMBRE[2] = "MARIA"NOMBRE[3] = "CARLOS"AINS(NOMBRE,2)NOMBRE[2] = "MANUEL"? NOMBRE[1]? NOMBRE[2]? NOMBRE[3]

4 Suprimir.

ADEL suprime elementos en una tabla redimensionándola.

ADEL(<array>,<expN>)

<array> - Nombre de la tabla<expN> - Posición elemento

Ejemplo_1:

DECLARE NOMBRE[3]NOMBRE[1] = "LUIS"NOMBRE[2] = "MARIA"NOMBRE[3] = "CARLOS"ADEL(NOMBRE,2)? NOMBRE[1]? NOMBRE[2]

5 Copiar.

La copia de un elemento o grupo de elementos de una tabla a otra tabla la realiza la función ACOPY, debiendo indicarse la tabla origen, la tabla destino, la posición inicial de la tabla origen, el nº de elementos a copiar y el elemento de la tabla destino donde ha de comenzarse la copia.

ACOPY(<array1>,<array2>[,<expN1> [,<expN2>[,<expN3>]]])

<array1> - Nombre de la tabla origen<array2> - Nombre de la tabla destino<expN1> - Posición origen en tabla origen a copiar

627

Page 628: Programacion en Java

<expN2> - Número de elementos a copiar desde <expN1><expN3> - Elemento destino a comenzar copia

Ejemplo_1:

DECLARE TABLA_A[2],TABLA_B[2]TABLA_A[1] = "LUIS"TABLA_A[2] = "MARIA"ACOPY(TABLA_A,TABLA_B)? TABLA_B[1]? TABLA_B[2]

Ejemplo_2:

DECLARE TABLA_A[2],TABLA_B[3]TABLA_A[1] = "A"TABLA_A[2] = "B"ACOPY(TABLA_A,TABLA_B,1,1,3)? TABLA_B[3]

6 Rellenar.

AFILL rellena uno o más elementos con la expresión indicada.

AFILL(<array1>,<expr>[,<expN1>[,<expN2>]])

<array1> - Nombre de la tabla<expr> - Expresión con la que se rellenar la tabla<expN1> - Posición donde comenzar a rellenar<expN2> - Número de elementos a rellenar desde <expN1>

Ejemplo_1:

DECLARE TLF[2]TLF[1] = "433-23-23"TLF[2] = "433-23-24"AFILL(TLF,"000-00-00",2,1)? TLF[1]? TLF[2]

7 Directorio.

ADIR accede al directorio del disco almacenado en tablas información relativa a los ficheros y directorios.

ADIR(<masc> [,<array1> [,<array2> [,<array3> [,<array4> [,<array5>]]]]])

<masc> - máscaras posibles en DOS (*/?) o nombre fichero.<array1> - Es la tabla que se rellenar con los nombres de ficheros reseñados en <masc>. Tipo C. <array2> - Idem. para tamaño en bytes de fichero. Tipo N.<array3> - Idem. para fechas. Tipo D<array4> - Idem. para horas. Tipo C<array5> - Idem. para atributos. Tipo C

628

Page 629: Programacion en Java

Atributos: A - Fichero archivo D - Directorio H - Oculto R - Sólo Lectura S - Sistema

Ejemplo_1:

fil_prg = ADIR("*.PRG")

Ejemplo_2:

DECLARE TABLA[ADIR("*.PRG")]

Ejemplo_3:

fil_sec = ADIR("*.sec")DECLARENOMBRE[fil_sec],FECHA[fil_sec] ADIR("*.sec",NOMBRE,"",FECHA)FOR n=1 TO fil_secfil_del = NOMBRE[n]IF FECHA[n] < CTOD("01/01/92") DELETE FILE &fil_delENDIFNEXT

8 Estructura.

La estructura de una base de datos puede conocerse mediante la función AFIELDS. Los nombres de campos, tipo, longitud, etc. pueden almacenarse en tablas para el posterior tratamiento.

AFIELDS(<array1> [,<array2> [,<array3> [,<array4> ]]]])

<array1> - Tabla que contendrá nombre de campos.<array2> - Tabla que contendrá tipo de campos.<array3> - Tabla que contendrá longitud de campos.<array4> - Tabla que contendrá número posiciones decima les.

Ejemplo_1:

USE basenum_cam = FCOUNT()DECLARE NOMBRE[num_cam],TIPO[num_cam] AFIELDS(NOMBRE,TIPO)FOR n=1 TO num_cam? NOMBRE[n]? TIPO[n]NEXT

9 Menú.

ACHOICE es una función que permite generar un menú de persiana con los elementos de una tabla en las posiciones de pantalla que se indiquen. Devuelve un valor de tipo numérico que se corresponde con el número de posición del elemento seleccionado. Si el valor es 0 no se seleccionó ningún elemento.

ACHOICE(<fila sup>,<colizq>,<fila inf>,<col dcha>,<array1> [,<array2>[,<func>[,<expN1>[,<expN2>]]]])

629

Page 630: Programacion en Java

<fila sup>- Coordenada X1 de pantalla<col izq> - Coordenada Y1 de pantalla <fila inf>- Coordenada X2 de pantalla<col dcha>- Coordenada Y2 de pantalla<array1> - tabla que contendrá elementos<array2> - tabla que contendrá valores lógicos<func> - Función de usuario.

Pasa 3 parámetros:1-modalidad: 0 Período de inactividad 1 Se intenta sobrepasar el principio2 Se intenta sobrepasar el final 3 Espera de tecla específica 4 No se puede escoger una opción2-elemento actual de la tabla

3-posición que ocupa el elemento en la ventanaValores retorno: 0 Suspende selección1 Devuelve elemento cursor 2 Contin#a proceso selección3 Va al elemento cuyo primercarácter corresponde a la última tecla oprimida.

Ejemplo_1:

CLEARSET SCOREBOARD OFFSET COLOR TO W+/N,,,,BG/NDECLARE MEN[3],LOG[3]MEN[1] = "ALTA "MEN[2] = "BAJA "MEN[3] = "MODIFICACION"LOG[1] = .T.LOG[2] = .T.LOG[3] = .T.clave = SPACE(2)@ 1,01 SAY "Clave: " GET clave PICTURE "XX" READDO CASECASE clave = "11"LOG[3] = .F.CASE clave = "22"LOG[2] = .F.OTHERWISERETURNENDCASE@ 4,1 TO 8,19opcion = ACHOICE(5,2,7,18,MEN,LOG)

10 Base Datos

DBEDIT visualiza el contenido de una base de datos en pantalla. Es una potente función que permite la edición de los datos sobre una ventana definida en pantalla.

DBEDIT([<fila sup>[,<colizq>,[<fila inf>,[<col dcha>]]]] [,<array1>],[,<func>][,<array2]/<expC>] [,<array3]/<expC>][,<array4]/<expC>] [,<array5]/<expC>][,<array6]/<expC>] [,<array7]/<expC>])

<fila sup> <col izq> <fila inf> <col dcha> posiciones.<array1> - Tabla de nombres de los campos.<func> - Función de usuario.

630

Page 631: Programacion en Java

<array2> - Tabla de modelos de visualización.<array3> - Tabla de encabezados de columnas.<array4> - Tabla de separación de encabezados. <array5> - Tabla de separación de columnas.<array6> - Tabla de separación de pies.<array7> - Tabla de pies.

Cuando se utiliza una función de usuario, DBEDIT() pasa de forma automática dos parámetros:1-Estado actual de DBEDIT() dependiendo de la última tecla pulsada antes de llamar a la función. Las diferentes modalidades del estado son:

0 Inactividad1 Se ha intentado sobrepasar el primer2 Se ha intentado sobrepasar el último registro3 El fichero de datos se encuentra vacio4 Se ha pulsado una tecla específica

2-Posición que ocupa en la tabla el campo sobre el que nos encontramos posicionados.Valores de retorno:

0 Para salir de DBEDIT()1 Para continuar la ejecución de DBEDIT() 2 Se vuelven a leer los datos nuevamente y se continúa DBEDIT()3 Se activa la posibilidad de añadir nuevos registros

Ejemplo_1:

DECLARETAB1[3],TAB2[3],TAB3[3],TAB4[3],TAB5[3],TAB6[3],TAB7[3]* Nombre campos

TAB1[1]= "BAS_LOC"TAB1[2]= "BAS_PRO"TAB1[3]= "BAS_HAB"

* Máscaras de visualización

TAB2[1]= "XXXXXXX"TAB2[2]= "XXXXXXX"TAB2[3]= "999,999,999"

* Encabezados de columna

TAB3[1]= "LOCALIDAD"TAB3[2]= "PROVINCIA"

TAB3[3]= "HABITANTES"

* Separadores de encabezados

TAB4[1]= "D"TAB4[2]= "D"TAB4[3]= "D"

631

Page 632: Programacion en Java

* Separadores de columnas

TAB5[1]= "3"TAB5[2]= "3"TAB5[3]= "3"

* Separadores de pies de página

TAB6[1]= "D"TAB6[2]= "D"TAB6[3]= "D"

* Pies de página

TAB7[1]= "DPIE_1D"TAB7[2]= "DPIE_2D"TAB7[3]= "DPIE_3D"

CLEARUSE BASEDBEDIT(1,1,7,40,TAB1,"",TAB2,TAB3,TAB4,TAB5,TAB6,TAB7)

XII. Impresora.

1. Salida.

SET DEVICE redirecciona las salidas por pantalla o por impresora. Por defecto es por pantalla.

SET DEVICE TO SCREEN/PRINTER

Ejemplo_1:

PROCEDURE LISTACURSOSIF !ISPRINTER() && Impresora no conectadaDO MSGIMPRESORA && Mensaje que conecteINKEY(0)ENDIFSET DEVICE TO PRINTUSE CURSOS INDEX CURSOSGO TOPNPAGINA = 1 && Contador de páginasNFILA = 5 && Contador de filas@ 0,0 SAY CHR(15) && Impresión comprimida

* CABECERA

@ 1,0 SAY "LISTADO CURSOS"@ 1,115 SAY "PAGINA.: " + LTRIM(STR(NPAGINA))@ 2,115 SAY "FECHA..: " + DTOC(DATE())@ 3,0 SAY "Nº"@ 3,10 SAY "NOMBRE CURSO"@ 3,90 SAY "PRECIO"@ 4,0 SAY REPLICATE(CHR(196),132)

632

Page 633: Programacion en Java

* FIN CABECERA

DO WHILE .NOT. EOF()@ NFILA, 0 SAY RECNO() PICTURE "9999"@ NFILA,10 SAY NOMCURSO@ NFILA,90 SAY PRECIONFILA = NFILA + 1IF NFILA = 50EJECT && Salto de páginaNPAGINA = NPAGINA + 1

* REPETICION DE LA CABECERA

NFILA = 5ENDIFSKIP && Incrementamos registroENDDOCLOSEEJECT@ 0,0 SAY CHR(18) && Desactivamos comprimidoSET DEVICE TO SCREENRETURN

2. Salto de página.

EJECT realiza un salto de página en la impresora, y pone a cero los valores de la fila y la columna de la impresora.

Use SETPRC() si necesita poner a cero los valores internos de fila y columna de la impresora sin enviar un salto de página.

EJECT

(Ver SET DEVICE, se incluye un ejemplo completo)

3. Conocer la situación del cabezal de impresión.

PCOL() devuelve la columna en que se halla el cabezal de impresión. Retorna un número entero.

Un EJECT (salto de página) coloca PCOL() a cero.

PCOL()

Ejemplo_1:

SET DEVICE TO PRINTER@ 10, PCOL() + 10 SAY "BANCO : " + BANCO

PROW() Devuelve la fila en que se haya el cabezal de impresión. Un salto de página, EJECT, coloca PROW() a cero.

PROW()

633

Page 634: Programacion en Java

Ejemplo_1:

SET DEVICE TO PRINTER@ PROW() + 1, 5 SAY "NOMBRE...: " + NOMBRE@ PROW() + 2, 5 SAY "DIRECCION: " + DIRECCION@ PROW() + 3, 5 SAY "POBLACION: " + POBLACION

4. Conocer si está preparada la impresora.

ISPRINTER() Comprueba si la impresora esta lista para imprimir. Devuelve un valor lógico.

ISPRINTER()

Ejemplo_1:

IF !ISPRINTER() && impresora no conectada@ 24,0 SAY "* CONECTE LA IMPRESORA, PULSE TECLA *"INKEY(0)ENDIF

5. Conocer si tiene papel.

DOSERROR() determina el error producido por el DOS. Devuelve un valor numérico correspondiente a un error. Para la lista de errores consulte el manual de Nantucket.

El error que genera la impresora por falta de papel es el número 28.

DOSERROR()

Ejemplo_1:

IF DOSERROR() = 28? "Falta papel"ENDIF

XIII. Funciones predefinidas.

1. De bases de datos.

ALIAS() Devuelve el alias del área de trabajo.

DELETED() Devuelve el estado de borrado del registro actual.

EOF() Indica si se alcanza el final de un archivo.

BOF() Indica si se alcanza el principio de un archivo.

DBFILTER() Determina la expresión del filtro.

FIELDNAME() Devuelve el nombre del campo especificado.

HEADER() Determina la longitud de cabecera.

634

Page 635: Programacion en Java

RECSIZE() Determina la longitud del registro.

FCOUNT() Devuelve el número de campos de la base.

USED() Determina la base de datos en uso.

FOUND() Devuelve verdadero si se encontró registro.

RECNO() Devuelve el número de registro actual.

LASTREC() Devuelve el número total de registros.

2. Numéricas.

ABS() Devuelve el valor absoluto de una expresión.

EXP() Calcula la exponencial.

INT() Convierte cualquier expresión numérica en entero.

LOG() Devuelve el logaritmo natural de un número.

MIN() Devuelve el valor mínimo de dos números o dos fechas.

MAX() Devuelve el valor máximo de dos números o dos fechas.

SQRT() Devuelve la raiz cuadrada de un número positivo.

ROUND() Devuelve el número redondeando con la cantidad de decimales especificados.

VAL() Convierte una tira de caracteres a un valor numérico.

3. Cadenas.

ASC() Devuelve el código ASCII del carácter izquierdo.

AT() Devuelve un número que indica la posición de comienzo de una cadena de caracteres dentro de otra.

CHR() Devuelve el carácter del código ASCII especificado.

EMPTY() Devuelve verdad si la expresión está vacia.

ISALPHA() Devuelve verdadero si el primer carácter es alfabético.

ISLOWER() Determina si el carácter más a la izquierda de la cadena está en minúsculas.

ISUPPER() Determina si el carácter más a la derecha de la cadena está en mayúsculas.

LEN() Devuelve el número de caracteres que hay en una cadena

635

Page 636: Programacion en Java

LEFT() Devuelve el número de caracteres especificados desde la izquierda

RIGHT() Devuelve el número de caracteres especificados desde la derecha.

LTRIM() Elimina los espacios de la izquierda de una cadena.

REPLICATE() Repite una expresión de caracteres.

SPACE() Crea una cadena de espacios.

STR() Convierte un valor numérico en cadena.

STRTRAN() Busca y reemplaza dentro de una cadena de caracteres.

SUBSTR() Extrae una parte específica de una cadena.

TRANSFORM() Devuelve la tira de caracteres con el formato especificado.

TRIM() Elimina los espacios de una cadena.

4. Fechas.

CDOW() Devuelve el nombre de día de la semana de una fecha.

CMONTH() Devuelve el nombre del mes de una fecha.

CTOD() Convierte a fecha una cadena

DATE() Devuelve la fecha del sistema

DAY() Devuelve el número de día del mes de una fecha.

DOW() Devuelve el número que representa el día de la semana de un valor fecha.

DTOC() Convierte una fecha a cadena

DTOS() Convierte una fecha a cadena tipo índice.

MONTH() Devuelve un número que representa el mes.

YEAR() Devuelve el valor número completo del año dada una fecha.

5. Hora.

SECONDS() Devuelve la hora del sistema como segundos y centésimas.

TIME() Devuelve la hora del sistema.

SECS() Devuelve hora como segundos y centésimas.

TSTGRING() Dada una cantidad de segundos nos devuelve dicha cantidad en formato hora.

636

Page 637: Programacion en Java

6. Otras funciones de interés.

FILE() Devuelve verdadero si existe el fichero especificado.

GETE() Recupera el contenido de una variable de entorno DOS.

TYPE() Devuelve el tipo de dato de la variable, expresión o campo.

COL() Devuelve la columna actual del cursor.

ROW() Devuelve la fila actual del cursor.

CURDIR() Determina el directorio actual.

DISKSPACE() Determina el número de bytes disponible en una unidad.

MEMORY() Devuelve el espacio de memoria libre.

READVAR() Devuelve el nombre de la variable de un GET/MENU.

XIV. Ordenes SET.

1. Ordenes SET TO...

SET ALTERNATE TO <fichero> Crea un fichero de protocolo.

SET COLOR TO <expresión> Fija los colores de pantalla.

SET DECIMALS TO <expN> Fija el número de los decimales a mostrar en los resultados de las funciones numéricas y cálculos.

SET DEFAULT TO <unidad>[:<rutra>] Especifica la unidad y directorio por defecto para la creación de ficheros.

SET DATE AMERICAN/ANSI/BRITISH/ITALIAN/FRENCH/GERMAN Fija el formato de los campos de fecha.

AMERICAN mm/dd/aaANSI aa.mm.ddBRITISH dd/mm/aaITALIAN dd-mm-aaFREMCH dd/mm/aaGERMAN dd.mm.aa.

SET DELIMITERS TO <expr> Especifica los caracteres empleados como delimitadores.

SET DEVICE TO SCREEN/PRINTER Dirige el resultado de la instrucción @ al dispositivo elegido.

SET FILTER TO <condición> Hace que la base de datos se vea como si sólo contuviese los registros que cumplen la condición.

637

Page 638: Programacion en Java

SET INDEX TO <lista ficheros> Abre el índice indicado y cierra los anteriores abiertos con la misma base de datos.

SET KEY <expN> TO <procedimiento> Asigna a una tecla un procedimiento.

SET MARGIN TO <expN> Fija el margen izquierdo de la impresora.

SET MESSAGE TO <expN>/CENTER Establece la línea donde se muestran los mensajes asociados a PROMPT.

SET ORDER TO [<expN>] Establece que fichero índice es el principal.

SET PATH TO [<lista de rutas>] Especifica la ruta de búsqueda que Clippersigue en el acceso a ficheros.

SET PRINTER TO [<dispositivo>/<fichero>] Determina la salida de la impresora.

SET PROCEDURE TO [<fichero>] Activa fichero de procedimientos.

2. Ordenes SET ON/OFF.

SET ALTERNATE on/OFF Determina cuando la salida se envía al fichero.

SET BELL on/OFF Determina cuándo suena la alarma durante la entradade datos.

SET CENTURY on/OFF Determina si una fecha debe mostrar los dígitos del siglo o no.

SET CONFIRM on/OFF Determina si se requiere pulsar return para cada GET.

SET CONSOLE on/off Determina si la ejecución de los comandos utilizarán la pantalla como salida..

SET CURSOR on/off Muestra u oculta el cursor en la pantalla.

SET DELETED on/OFF Oculta/procesa los registros marcados para borrar.

SET DELIMITERS on/OFF Determina si se muestran los delimitadores

SET ESCAPE ON/off Activa/Desactiva el desvío producido al pulsar la tecla ESC.

SET INTENSITY ON/off Muestra los campos de entrada durante los GETs en color o en vídeo inverso.

SET PRINT on/OFF Determina si la salida de los comandos @...SAY se mandarán a la impresora.

SET SCOREBOARD ON/off Determinan si los mensajes de clipper aparecen en la línea 0.

SET SOFTSEEK on/OFF Permite acceder al registro más próximo si el buscado no se encuentra.

SET UNIQUE on/OFF Determina si sólo los registros con clave no repetida aparecerán en el índice.

638

Page 639: Programacion en Java

SET WRAP on/OFF Permite el movimiento circular entre opciones de menús.

XV. Redes locales.

1. Bloqueo de registro.

RLOCK() Bloquea/desbloquea el registro actual del área de trabajo en curso. Para utilizar en redes locales.

RLOCK() / LOCK()

2. Bloqueo de ficheros.

FLOCK() Bloquea/desbloquea un archivo abierto de base de datos dependiendo de su estado anterior. Sólo se utiliza en redes locales.

FLOCK()

3. Desbloqueo.

UNLOCK Desactiva el bloqueo de los archivos o registros bloqueados por el #ltimo usuario.

UNLOCK [ALL]

ALL - Quita todos los bloqueos en curso de todas las áreas de trabajo.

4. Uso exclusivo de ficheros.

SET EXCLUSIVE Permite el uso exclusivo o no de archivos de base de datos, índices y campos memos, en redes locales. Por defecto esta en ON.

SET EXCLUSIVE ON/off

639