Tipos escalares en ADA

En este capítulo se echan las bases de los aspectos a pequeñas escala de Ada. Se comienza con la declaración de objetos, la asignación de valores a ellos y las idea de rango de validez y visibilidad.Se introducen los importantes conceptos de tipo, subtipos y restricciones.

    1. Declaración de objetos y asignaciones

Los valores (datos) pueden almacenarse en objetos que son de un cierto tipo (lo que se indica al momento de su declaración). Estos objetos pueden ser variables o constantes. Una variable se  declara mediante su nombre (un identificador), su tipo y (posiblemente ) un valor inicial. Por ejemplo:

I : INTEGER;           — variable de nombre I y de tipo entero
P: INTEGER := 38; — variable de nombre P, de tipo entero y valor inicial 38
Es posible declarar varias variables simultáneamente separándolas por comas. Por ejemplo: I,J,K : INTEGER;
P,Q,R: INTEGER:= 38;

Si se declara una variable sin un valor inicial es necesario tener cuidado de no usarla con un valor indefinido. Si un programa usa un valor indefinido de una variable no inicializada, su comportamiento puede se impredecible. En este caso el programa debería considerarse estrictamente erróneo, sin embargo tanto el compilador como el sistema de run-time podrían no ser capaces de detectar el problema.
La manera más simple de asignar un valor a una variable es mediante el comando de asignación (:=). Por ejemplo:

I:= 36; P:=Q + R;

Existe una gran similitud entre una declaración con valor inicial y el comando de asignación. Ambos usan := antes de la expresión, la cual puede ser de cualquier nivel de complejidad. Una diferencia importante es que es posible inicializar varias variables simultáneamente, pero no es posible usar un único comando de asignación para varias variables. Esto parece poco práctico, sin embargo la necesidad de asignar un mismo valor a varias variables generalmente sólo surge al momento de inicializarlas.
Una constante se declara de forma similar a una variable, escribiendo la palabra constant después de los dos puntos. Lógicamente, una constante debe ser inicializada al ser declarada, de otro modo pierde su sentido. Por ejemplo:

PI : constant REAL :=3.1415926536;

Ejercicio: Indique los errores de las siguientes declaraciones y comandos:

  1. var I:INTEGER;
  2. G:constant :=981;
  3. P,Q:constant INTEGER; d)  P:=Q:=7;
  4. MN:constant INTEGER:=M*N;
  5. 2PI:constant:= 2.0*PI;

 

 

 

 

    1. Bloques y ámbito de validez (scope)

Ada hace una clara distinción entre la declaraciones de nuevos identificadores y los instrucciones que los usan. Es obvio que las primeras deben preceder a los últimos. Un bloque (block) es la estructura más simple que incluye declaraciones y comandos.
Un  bloque  comienza  con  la  palabra  reservada  declare,  algunas  declaraciones,  begin,  algunas instrucciones y concluye con la palabra reservada end y un punto y coma. Por ejemplo:

declare
I: INTEGER:=0;
begin
I:=I+1;
end;

Un bloque es un caso particular de una instrucción, por lo tanto una de sus instrucciones  internas puede ser otro bloque.
Cuando se ejecuta una instrucción “bloque” se prepara su parte declarativa (entre declare y begin) y luego se ejecutan las instrucciones  (entre begin y end). Nótese la terminología: se preparan las declaraciones y se ejecutan las instrucciones. La preparación de una declaración consiste en “crear” el objeto declarado y asignarle (si corresponde) un valor inicial. Al llegar al final (end) del bloque todas las cosas declaradas en él automáticamente dejan de existir.
Un  punto  importante  es  que  los  objetos  usados  para  la  inicialización  de  otros  objetos  deben,
lógicamente, existir. Es decir, toda declaración debe preceder a su uso. Por ejemplo:

declare
I: INTEGER :=0; K: INTEGER:= I;
begin

está permitido, pero

declare
K: INTEGER:= I; I: INTEGER:= 0;
begin

en general no. (¿En qué situación esto estaría correcto, aunque con un significado diferente?)
Al  igual  que  otros  lenguajes  Ada  maneja  la  idea  de  ocultamiento  (hiding).       Consideremos  el siguiente bloque

declare
I,J: INTEGER;
begin
. . .                    — aquí es válida la variable I externa
declare
I: INTEGER;
begin
. . .              — aquí es válida la variable I interna
end;
. . .                     — aquí es válida la variable I externa
end;

La declaración interna de I no hace que la externa desaparezca, sino que ésta se torna temporalmente invisible. Al terminar el bloque interno, la variable I interna deja de existir y la externa vuelve a estar visible.
Hay que distinguir entre el ámbito de validez (scope) y la visibilidad de un objeto. Por ejemplo, en el caso de un bloque el ámbito de validez de una variable (o constante) se extiende desde el punto de su declaración hasta el final (end) del bloque. Sin embargo, una variable (o constante) no es visible en su propia declaración ni en ningún bloque interno donde su nombre sea utilizado para la declaración de otro objeto.

Para inicializar K se utiliza el valor de I externa, puesto que la declaración de K precede a la de la variable I interna. Vemos pues que el código

K: INTEGER:= I; I: INTEGER:= 0;

puede o no ser válido, depende del contexto.

Ejercicio: Indique qué errores hay en la siguiente sección de código.

declare
I: INTEGER:= 7; J,K: INTEGER
begin
J:= I+K;
declare
P: INTEGER = I; I,J: INTEGER;
begin
I:= P+Q; J:= P -Q; K:= I* J;
end; PUT(K);
end;

 

    1. Tipos

Un tipo queda totalmente caracterizado por un conjunto de valores y un conjunto de operaciones. En el caso del tipo de datos primitivo INTEGER, el conjunto de valores está representado por

. . . -3, -2, -1, 0, 1, 2, 3 . . .

y las operaciones por

+, -, *, etc.

Se asume que los tipos primitivos del lenguaje están definidos en el paquete STANDARD.
Los valores de un tipo no pueden ser asignados a variables de otro tipo. Esta una regla fundamental del manejo estricto de tipos.
La declaración de tipos se realiza con una sintaxis distinta a la declaración de variables y constantes para enfatizar la diferencia conceptual existente. La declaración se realiza mediante la palabra reservada type, el identificador asociado con el tipo, la palabra reservada is y la definición del tipo seguida de un punto y coma. Por lo tanto, el paquete STANDARD debería contener declaraciones tales como

type INTEGER is . . ;

 

 

ejemplo:

 

La definición entre is y ; de alguna manera entrega el conjunto de  valores asociados al tipo. Por

 

type COLOUR is (RED, AMBER, GREEN);

 

 

 

Se ha definido un nuevo tipo denominado COLOUR, al que se asocia un conjunto de 3 valores: RED, AMBER y GREEN, es decir, las variables de tipo COLOUR sólo podrán “almacenar” alguno de dichos valores. Por ejemplo:

C: COLOUR;
D: COLOUR:=RED;
DEFAULT: constant COLOUR:=GREEN;

son declaraciones válidas.
Debido al manejo estricto de tipos no podemos “mezclar” colores y enteros, por ejemplo:

I: INTEGER; C:COLOUR;
. . . I := C;

es incorrecto. Comparemos esto con la “filosofía” del leguaje C (Keninghan, B. Ritchie D. El Lenguaje de Programación C).
Una enumeración es una lista de valores enteros constantes, como en

enum boolean  {NO, YES};

El primer nombre en un enum tiene un valor 0, el siguiente 1, y así sucesivamente, a menos que sean especificados valores explícitos. Los valores no especificados continúan la progresión a partir del último valor que sí lo fue, como en el segundo de esos ejemplos:

enum escapes  { BELL = ´\a´, RETROCESO = ´\b´, TAB = ´\t´, NVALIN = ´\n´, VTAB = ´\v´, RETURN = ´\r´};

enum months { ENE = 1, FEB, MAR, ABR, MAY, JUN,
JUL, AGO, SEP, OCT, NOV, DIC};

Las enumeraciones proporcionan una manera conveniente de asociar valores constantes con nombres, son una alternativa a #define. Aunque las variables de tipos enum pueden ser declaradas, los compiladores no necesitan revisar que lo que se va a almacenar en tal variable es un valor válido para la enumeración. No obstante, las variables de enumeración ofrecen la oportunidad de revisarlas y tal cosa es a menudo mejor que #define. Además, un depurador puede ser capaz de imprimir los valores de variables de enumeración en su forma simbólica.
Como puede verse, en Ada existen mecanismos totalmente diferentes para definir constantes y para
definir tipos de enumeración, en cambio en C los tipos enum sirven para ambos acciones. Además, lo dicho respecto a tipos enum en C es un claro ejemplo de la estilo “relajado” de manejo de tipos de este lenguaje.

 

    1. Subtipos

Un subtipo, como su nombre lo indica, es un subconjunto de los valores de otro tipo, al que se le denomina tipo base. Dicho subconjunto se define mediante una restricción. Si embargo no hay forma de restringir el conjunto de operaciones de tipo base.
Por ejemplo, supongamos que queremos manipular días; sabemos que los días de un mes están en el
rango de 1 a 31, entonces el subtipo (del tipo INTEGER) sería

subtype DAY_NUMBER is INTEGER range 1 .. 31;

Posteriormente podemos declarar variables y constantes usando el identificador de un subtipo exactamente igual a como lo hacemos con un identificador de tipo. Por ejemplo, al declarar

D: DAY_NUMBER;

estamos asegurando que la variable D pude tomar sólo valores enteros en el rango de 1 a 31. En tiempo de compilación podrían detectarse errores como

D := 32;

Además, el compilador agregará chequeos de tiempo de ejecución (run time checks) para verificar que se cumpla la restricción, si fallase un chequeo se indicaría un excepción CONSTRAIN_ERROR.
Es importante notar que la declaración de un subtipo no introduce un nuevo tipo. Un objeto como D es de tipo entero y por lo tanto el siguiente código es totalmente legal

 

 

D: DAY_NUMBER; I: INTEGER;
. . . D:=I;

Lógicamente, durante la ejecución, el valor de I podría salirse del rango 1 a 31. En este caso se indicaría CONSTRAIN_ERROR. Pero una asignación en el sentido inverso

I := D;

siempre funcionará correctamente.

No es necesario declarar un  nuevo tipo para imponer una restricción. Por ejemplo, mediante D: INTEGER range 1 .. 31;
se ha declara una variable de tipo entero junto con una restricción.

Ejercicio:

  1. ¿Cuándo tendrá sentido definir un tipo con una cierta restricción  y cuándo una variable?
  2. ¿Qué diferencia semántica habrá entre las siguientes declaraciones?
    1. subtype DAY_NUMBER is INTEGER range 1 .. 31;
    2. type DAY_NUMBER is range 1 .. 31;

 

    1. Tipos numéricos simples

La complejidad de los problemas de análisis numérico (estimación de errores y otros) se refleja en los tipos de datos numéricos de Ada. Sin embargo, aquí expondremos sólo los aspectos más simples de los tipos entero (INTEGER) y real (REAL), para usarlos en nuestros ejemplos.
El valor mínimo del tipo INTEGER está dado por INTEGER´FIRST y el máximo por INTEGER´LAST. Estos son dos ejemplos de atributos, los cuales se denotan mediante un apóstrofe seguido de un identificador.
El valor de INTEGER´FIRST dependerá de la implementación y siempre será negativo. En una máquina de 16 bits (con complemento a dos) se tendrá

INTEGER´FIRST = -32768
INTEGER´LAST = +32767

Por supuesto que para lograr una mejor portabilidad debemos usar los atributos FIRST y LAST en lugar de los valores -32768 y +32767. Dos subtipos muy útiles son

subtype NATURAL is INTEGER range 0 .. INTEGER´LAST;
subtype POSITIVE is INTEGER range  1 .. INTEGER´LAST;
Los atributos FIRST y LAST se aplican automáticamente a los subtipos, por ejemplo POSITIVE´FIRST = 1
NATURAL´LAST = INTEGER´LAST

También es posible hacer restricciones sobre los reales. Además, los atributos FIRST y LAST también son aplicables.
A continuación se resumen algunas de las operaciones predefinidas para los tipos entero y real más importantes.

1) +, – Estos son tanto operadores unarios como binarios. En el primer caso el operando puede ser entero o real; el resultado será del mismo tipo. El operador unario + en realidad no hace nada, pero el operador unario – cambia el signo del operando. Cuando se usan como operadores binarios, ambos operandos deben ser del mismo tipo y el resultado será del tipo que corresponda. Su acciones son la adición y sustracción  normales.

      1. *     Multiplicación;  ambos  operandos  deben  ser  enteros  o  reales;  el  resultado  será  del  tipo correspondiente.
      2. /  División;  ambos operandos deben ser del mismo tipo. La división entero trunca el resultado.
      3. rem  Resto; los operandos deben ser enteros y el resultado también lo es. Entrega el resto de la división.
      4. mod  Módulo; ambos operandos deben ser enteros y el resultado también lo es. Corresponde a la operación matemática módulo.
      5. abs  Valor absoluto; es un operador unario y el operador debe ser entero o real. El resultado es del mismo tipo y corresponde al valor absoluto del operando.
      6. ** Exponenciación; eleva el primer operando a la potencia indicada en el segundo. Si el primer operando es un entero, entonces el segundo debe ser un entero positivo o cero. Si el primer operando es un real, entonces el segundo puede ser cualquier entero. El resultado es del mismo tipo del primer operando.

Además, pueden realizarse las operaciones =, /=, <, <=, > y >= para obtener resultados de tipo booleano TRUE o FALSE. También en este caso los operadores deben ser del mismo tipo.
A pesar que estas operaciones son bastante sencillas es necesario hacer algunas observaciones.
La regla general es que no se puede mezclar valores de distinto tipo en una expresión aritmética. Por ejemplo, no se puede sumar un entero a un real. El cambio de un valor INTEGER a uno REAL o viceversa puede hacerse usando el nombre del tipo (o subtipo) de dato que se necesita como si fuera el nombre de una función.   Por ejemplo, si tenemos

I: INTEGER:=3; R:REAL:=5.6;

no podemos escribir

I + R

pero si es válida la expresión REAL(I) + R

 

 

que usa la “suma real” para entregar 8.6, o

I + INTEGER(R)

que entrega 9, al hacer uso de la “suma entera”.
La conversión de real a entero siempre entrega un valor redondeado,  por ejemplo:

1.4 entrega 1
1.6 entrega 2

Dependiendo de la implementación los valores intermedios (como 1.5) serán redondeados hacia arriba o hacia abajo.
Finalmente, hagamos algunas observaciones respecto al operador **. Para un exponente positivo (es decir, un entero positivo) esta operación corresponde a una multiplicación repetida. Por ejemplo:

3**4 = 3*3*3*3 = 81
3.0**4 = 3.0*3.0*3.0*3.0* = 81.0
Cuando el segundo operando es cero, el resultado será obviamente uno 3**0 = 1
3.0**0 = 1.0

El exponente no puede ser negativo si la base es entera, ya que el resultado podría no ser entero. Pero si la base es real este operador entrega el correspondiente recíproco

3.0**(-4) = 1.0/81.0 = 0.0123456780123…

Como es usual existen diferentes niveles de precedencia entre los operadores en una expresión, y, además, la precedencia natural puede ser alterada con el uso de paréntesis. Los operadores de igual jerarquía son aplicados de izquierda a derecha. La jerarquía de operados en Ada es la siguiente

=  /=  <  <=  >  >=
+   –                        (binarios)
+  –                          (unarios)

/ * mod rem abs **  
 

Por ejemplo:

   
 

A/B*C A+B*C+D A*B+C*D
A*B**C

 

significa significa significa significa

 

(A/B)*C A+(B*C)+D (A*B)+(C*D)
A*(B**C)

Como ya se indicó, por omisión los operadores de un mismo nivel se aplican de izquierda a derecha, sin embargo, no está permitido escribir varios operadores de exponenciación sin el uso de paréntesis. Por esto la expresión

A**B**C

es incorrecta y debe escribirse

(A**B)**C          o              A**(B**C)

De esta manera se evita el riesgo de que accidentalmente se escriba una expresión sintácticamente correcta, pero semánticamente errónea.
Es necesario tener cuidado con los operadores unarios, por ejemplo:

-A**B      significa      -(A**B)      y no     (-A)**B además, las expresiones
A**-B   y  A*-B

son ilegales. Es necesario, en estos casos, utilizar paréntesis para definir claramente lo que se desea calcular. Ejecicio:

 

 

  1. Evalúe las siguientes expresiones: I:INTEGER:=7;

J:INTEGER:=-5; K:INTEGER:=3;

  1. I*J*K
  2. I/J*K
  3. I/J/K
  4. J+2 rem I
  5. K**K**K

 

  1. Escriba las siguientes expresiones matemáticas en Ada. Defina identificadores apropiados.
    1. b2 – 4ac
    2. ? r3

 

    1. Tipos de enumeración

Primero mostremos algunos ejemplos de tipos de enumeración:

type COLOUR is (RED,AMBER,GREEN);
type  DAY is (MON, TUE, WED, THU, FRI, SAT, SUN);
type STONE is (BERYL, QUARTZ);
type SOLO is (ALONE);

No existe un límite superior para la cantidad de valores de un tipo de enumeración, pero debe haber al menos uno.
Las restricciones de los tipos enumerados son semejantes a las restricciones sobre los enteros. Por
ejemplo:

subtype WEEKDAY is DAY range MON .. FRI; D: WEEKDAY;

Si el límite superior es inferior al límite inferior se obtendrá un rango vacío. Por ejemplo:

subtype COLOURLESS is COLOUR range AMBER .. RED;
Los atributos FIRST y LAST también son aplicables a los tipos de enumeración, entonces COLOUR´FIRST = RED
WEEKDAY´LAST = FRI

Otros atributos predefinidos entregan el sucesor (SUCC) y el predecesor (PRED) de un cierto valor de un tipo de enumeración. Por ejemplo:

COLOUR´SUCC(AMBER) = GREEN STONE´SUCC(BERYL) = QUARTZ DAY´PRED(FRI) = THU

Lógicamente, lo indicado entre paréntesis es cualquier expresión válida del tipo correspondiente. Si se intenta obtener el antecesor (sucesor) del primer (último) valor el sistema entregará una excepción CONSTRAIN_ERROR.
Otro atributo predefinido es POS, el cual entrega la posición del valor dentro de la declaración del tipo (empezando desde cero). El atributo contrario a POS es VAL, que entrega el valor asociado a una cierta posición. Por ejemplo:

COLOUR´POS(RED) = 0
COLOUR´POS(AMBER) = 1
COLOUR´POS(GREEN) = 2 COLOUR´VAL(0) = RED DAY´VAL(6) = SUN
SOLO´VAL(1)   — CONSTRAIN_ERROR

Ada incluye los atributos POS y VAL, puesto que podrían ser necesarios en algunas situaciones, pero no se recomienda su uso pues no es una buena práctica de programación. Es mejor pensar en términos de valores de enumeración que en números.
Puesto que se establece un orden entre los valores de un tipo de enumeración , es posible aplicar los operadores  =, /=, <, <=, > y  >=. Por ejemplo:

 

 

RED < GREEN  es   TRUE WED >= THU  es   FALSE

Ejercicio:

  1. Evalúe
  2. DAY´SUCC(WEEKDAY´LAST)
  3. WEEKDAY´SUCC(WEEKDAY´LAST)
  4. STONE´POS(QUARTZ)
  5. Declare los siguientes tipos
  6. frutas típicas.
  7. proveedores de computadores.
  8. Si el primer día del mes está en la variable D de tipo DAY, escriba una asignación  que               reemplace D por el día de la semana del N-ésimo día del mes.

 

    1. Tipo Booleano

El tipo booleano es un tipo de enumeración predefinido, cuya declaración puede considerarse como

type BOOLEAN is (FALSE, TRUE);

Los valores booleanos son producidos por los operadores =, /=, <, <=, > y >=, los que tienen el significado normalmente aceptado y se aplican a muchos tipos. Por ejemplo:

if TODAY = SUN then
TOMORROW := MON;

 

else end if;

 

TOMORROW:= DAY´SUCC(TODAY);

 

Existen otros operadores asociados al tipo BOOLEAN.

  1. not  Es un operador unario y cambia TRUE a FALSE y viceversa.
  2. and  Es un operador binario. El resultado es TRUE si ambos operadores son TRUE, en otro           caso será FALSE.
  3. or Es un operador binario. El resultado es TRUE si al menos uno de los operadores es TRUE, es FALSE si ambos operadores son FALSE.
  4. xor Es un operador binario. El resultado es TRUE si y sólo si ambos operadores son distintos, es decir, uno es TRUE y el otro es FALSE.

 

Los operadores and, or y xor tienen la misma precedencia, la cual es menor que cualquier otro operador. En particular, tienen menor precedencia que =, /=, <, <=, > y >=. Como consecuencia de esto no se necesitan paréntesis para expresiones como

P < Q and  I = J

Sin embargo, a pesar de tener la misma precedencia, los operadores and, or y xor no pueden mezclarse sin paréntesis, entonces

B and C or D

es incorrecto y debe escribirse
B and (C or D)        o          (B and C) or D para enfatizar el sentido de la expresión.
Los programadores familiarizados con otros lenguajes recordarán que los operadores and y or tienen distinta precedencia. Esto puede conducir a errores, por ello Ada les da la misma precedencia y obliga a usar paréntesis. Obviamente, si se aplica el mismo operador en forma sucesiva no es necesario usar paréntesis ya que tanto and como or son asociativos. Por ejemplo:

B and C and D

es una expresión correctamente escrita.
Las variables y constantes booleanas   pueden ser declaradas y utilizadas en la manera usual. Por
ejemplo:

DANGER: BOOLEAN;

 

 

SIGNAL: COLOUR;
. . .
DANGER := SIGNAL = RED;

La variable DANGER será TRUE si la señal es RED. Además, podríamos escribir

if DANGER then
STOP_TRAIN;
end if;

Notemos que no deberíamos escribir

if DANGER = TRUE then

porque a pesar que es sintácticamente correcto no toma en cuenta que DANGER es una variable booleana y puede utilizarse directamente como una condición. Peor aún sería escribir

if  SIGNAL = RED then
DANGER .= TRUE;

 

else end if;

 

DANGER := FALSE;

 

en lugar de            DANGER := SIGNAL = RED;

Ejercicio:

  1. Escriba declaraciones de constantes T y F  con los valores TRUE y FALSE.

Evalúe  (A /= B) = (A xor B) para todos los valores posibles de las variables booleanas A y B.

 

 

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *