Tipos Compuestos en ADA





En esta sección se describirán los tipos compuestos, es decir, arreglos y registros. Además, con la introducción de los caracteres y strings  se completará la discusión sobre los tipos de enumeración.

 

 

Arreglos

 

Un arreglo es un objeto compuesto que consiste de un grupo de componentes todos del mismo tipo. Un arreglo puede ser de una, dos o más dimensiones. Una declaración típica de un arreglo es

 

A: array (INTEGER range 1..6) of REAL

 

Aquí se declara la variable A como un objeto que contiene 6 componentes, cada uno de los cuales es de tipo REAL. Para referirse a los componentes individuales se debe indicar el nombre del array seguido de una expresión entre paréntesis que entrega un valor entero dentro del rango discreto 1..6. Si el resultado de esta expresión, conocida como el valor del índice, no está dentro del rango especificado, el sistema emitirá la excepción CONSTRAIN_ERROR. Para colocar ceros en cada componente de A podríamos escribir

 

for I in 1..6 loop

A(I) := 0.0;

end loop;

 

Como se dijo, un arreglo puede tener varias dimensiones, en cuyo caso se debe indicar un rango para cada dimensión. Entonces

 

AA: array (INTEGER range 0..2, INTEGER range 0..3) of REAL;

 

es un arreglo de 12 componentes, cada uno de los cuales es referenciado mediante dos índices enteros, el primero dentro del rango 0..2 y el segundo en el rango 0..3. Para colocar todos estos elementos en cero podríamos escribir

 

 

for I in 0 .. 2 loop for J in 0 .. 3 loop

AA(I,J) := 0.0;

end loop; end loop;

Los rangos no deben ser necesariamente estáticos, por ejemplo, es posible escribir N: INTEGER:= . . .;

. . .

B: array (INTEGER range 1..N) of BOOLEAN;

 

así, la cantidad de componentes de B estará determinada por el valor N, el que lógicamente deberá tener un valor antes de realizar la elaboración de B.

Los rangos discretos en los arreglos siguen reglas similares a los rangos de los sentencias for. Una de ellas es que un rango de la forma 1..6 implica que el tipo asociado es INTEGER, de ahí que podamos escribir

 

A: array (1..6) of REAL;

 

 

 

 

ejemplo


Sin embargo, el índice de un arreglo puede ser de cualquier tipo discreto. Podríamos tener por

 

 

HOURS_WORKED: array (DAY) of REAL;

 

Este arreglo tiene siete componentes, desde HOURS_WORKED(MON) hasta HOURS_WORKED(SUN). Podemos usar este arreglo para almacenar las horas de trabajo para cada día de la semana

 

 

 

 

 

 

for D in WEEKDAY loop

HOURS_WORKED(D) := 8.0;

end loop; HOURS_WORKED(SAT) := 0.0;

HOURS_WORKED(SUN) := 0.0;

 

Si quisiésemos que el arreglo sólo tuviera cinco elementos, uno para cada día de lunes a viernes, podríamos escribir

 

HOURS_WORKED: array (DAY range MON..FRI) of REAL; o mejor

HOURS_WORKED: array (WEEKDAY) of REAL;

 

Los arreglos tienen varios atributos relacionados con sus índices. A’FIRST y A’LAST entregan, respectivamente, los límites inferior y superior del primer (o único) índice de A. Entonces, de acuerdo a la última declaración de HOURS_WORKED

 

HOURS_WORKED’FIRST = MON HOURS_WORKED’LAST = FRI

 

A’LENGTH entrega la cantidad de valores del primer (o único) índice de A. HOURS_WORKED’LENGTH = 5

A’RANGE es una forma abreviada para A’FIRST .. A’LAST. Entonces HOURS_WORKED’RANGE = MON .. FRI

Los mismos atributos pueden aplicarse a los demás índices de un arreglo multidimensional, para ello se debe agregar la dimensión (una expresión entera estática) involucrada entre paréntesis. Entonces, en el caso de nuestro arreglo bidimensional AA tenemos

 

AA’FIRST(1) = 0

AA’FIRST(2) = 0

AA’LAST(1) = 2

AA’LAST(2) = 3

AA’LENGHT(1) = 3

AA’LENGHT(2) = 4

AA’RANGE(1) = 0 .. 2

AA’RANGE(2) = 0 .. 3

 

El atributo RANGE es particularmente útil con iteraciones. Nuestros ejemplos anteriores quedarían mejor escritos como

 

for I in A’RANGE loop

A(I) := 0.0;

end loop;

 

 

for I in AA’RANGE(1) loop for J in AA’RANGE(2) loop

AA(I,J) := 0.0;

end loop; end loop;

 

El atributo RANGE también puede utilizarse en declaraciones, por ejemplo J: INTEGER range A’RANGE;

es equivalente a

 

J: INTEGER range 1 .. 6;

 

En  lo  posible, es mejor utilizar los atributos para reflejar las relaciones entre entidades de un programa, de este modo los cambios quedan restringidos a las declaraciones sin que, en general, impliquen

 

 

 

 

cambios en el resto del código. Por ejemplo, si cambiamos los rangos de los arreglos A y AA, no será necesario modificar los ciclos for usados para asignarles valores.

Los arreglos que hemos visto hasta el momento son variables en el sentido normal. Por lo tanto, se

les puede asignar valores y ser usadas en expresiones. Al igual que otras variables, a los arreglos se les puede asignar un valor inicial. Esto generalmente se denota mediante un conjunto que es la notación literal de un arreglo. La forma más simple de realizar esta asignación es mediante una lista ordenada (encerrada entre paréntesis) de expresiones (separadas por comas) que entregan los valores de los componentes. Por ejemplo, para inicializar el arreglo A con ceros, escribimos

 

A: array (1 .. 6) of REAL := (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);

En el caso de arreglos multidimensionales la lista será una lista con sublistas, por ejemplo AA: array (0 .. 2, 0 .. 3) of REAL := ( (0.0,  1.0,  2.0,                                                        3.0),

(4.0,  5.0,  6.0,     7.0),

(8.0,  9.0,  10.0, 11.0) );

De acuerdo a esta declaración, tenemos AA(0,0) = 0.0

AA(0,1) = 1.0

AA(0,2) = 2.0

AA(0,3) = 3.0

AA(1,0) = 4.0

etc.

 

Nótese que la lista debe estar completa, es decir, si se desea inicializar un componente del arreglo, entonces deben inicializarse todos, y en el orden correcto.

Es posible declarar un arreglo como constante, en cuyo caso, obviamente, es obligatorio dar un valor inicial. Este tipo de arreglos es útil para implementar tablas. En el siguiente ejemplo se usa un arreglo constante para determinar si un día en particular es día de trabajo o no.

 

WORK_DAY: constant array (DAY) of BOOLEAN := (TRUE, TRUE, TRUE, TRUE, TRUE,

FALSE, FALSE);

 

Un ejemplo interesante lo constituye un arreglo constante que permite determinar el día siguiente, sin preocuparse del último día de la semana.

 

TOMORROW: constant array (DAY) of DAY := (TUE, WED, THU, FRI, SAT, SUN, MON);

 

Finalmente, notemos que los componentes de un arreglo pueden ser de cualquier tipo o subtipo. Además, las dimensiones de un arreglo multidimensional pueden ser de diferentes tipos discretos. Un ejemplo un tanto extraño, pero válido, sería

 

STRANGE: array (COLOUR, 2 .. 7, WEEKDAY range TUE .. THU)

of PLANET range MARS .. SATURN;

 

 

Ejercicio:

 

  1. Declare un arreglo F de enteros cuyo índice varíe entre 0 y N. Además, escriba código Ada para asignar a los componentes de F su correspondiente valor de la serie de Fibonaci.

 

F0 = 0,  F1 = 1, Fi = Fi-1 + Fi-2

 

  1. Escriba código Ada para encontrar los índices I y J de componente con mayor valor del arreglo A:array (1 .. N, 1 .. M) of REAL;

 

  1. Declare un arreglo DAYS_IN_MONTH que permita determinar el número de días de cada mes.

 

  1. Declare un arreglo YESTERDAY análogo al arreglo TOMORROW dado en un ejemplo anterior.

 

  1. Declare un arreglo BOR tal que: BOR(P,Q) = P or Q

 

  1. Declare una matriz constante unitaria UNIT de orden 3. Una matriz unitaria es aquella que tiene unos en su diagonal principal y ceros en todos los demás componentes.

 

 

 

 

Tipos arreglo

 

Los arreglos hasta ahora vistos no tienen un tipo explícito. De hecho son de tipo “anónimo”.

Reconsiderando el primer ejemplo del punto anterior, podemos escribir la   definición de tipo

 

type VECTOR_6 is array (1 .. 6) of REAL;

 

y luego declarar  A usando el tipo definido  de la manera ya conocida A: VECTOR_6;

Una ventaja de usar tipos es que es posible realizar asignaciones de arreglos completos que han sido declarados separadamente.  Por ejemplo, si además declaramos

 

B:VECTOR_6;

 

podemos escribir

 

B:= A;

 

que sería una forma abreviada y más recomendable de B(1):= A(1); B(2):= A(2); . . .B(6):= A(6);

Por otro lado, si escribimos

 

C: array (1 .. 6) of REAL; D: array (1 .. 6) of REAL;

 

la expresión C:= D; será ilegal debido a que C y D no son del mismo tipo. Son de diferentes tipos, ambos anónimos. La regla que subyace en todo esto es que cada definición de tipo introduce un nuevo tipo y en este caso la sintaxis nos dice que una definición de tipo arreglo es el trozo de texto que comienza con la palabra array hasta el punto y coma (exclusive). Incluso, si escribimos

 

C,D: array (1 .. 6) of REAL;

 

la expresión C:= D; seguirá siendo incorrecta, esto porque esta declaración múltiple es sólo una forma abreviada de las dos declaraciones anteriores.

El que se defina o no un nuevo tipo depende mucho del grado de abstracción que se pretenda lograr. Si se está pensando en los arreglos como objetos que serán manipulados (asignados, mezclados, etc..) entre ellos, se hace necesario definir un tipo. Si, por otro lado, se conceptualiza al arreglo sólo como una agrupación de elementos indexables, sin relación alguna con otros arreglos semejantes, entonces lo más recomendable es definirlo como de tipo anónimo.

El modelo de tipos arreglo presentado no es del todo satisfactorio, pues no nos permite representar una abstracción que agrupe arreglos con diferentes límites, pero que para todos los demás efectos forman una sola familia. En particular, no nos permite escribir subprogramas que puedan tomar arreglos de largo variable como parámetros reales. De ahí que Ada introduce el concepto de tipo arreglo no restringido, en el cual no se expresan los límites al momento de la declaración. Consideremos

 

type VECTOR is array (INTEGER range <>) of REAL; (Al símbolo compuesto <> se le denomina “caja”).





Con  esto  se  está  diciendo  que  VECTOR  es  el  nombre  de  un  tipo  arreglo  unidimensional  de

componentes REAL con índice INTEGER. Sin embargo, los límites del rango no se han especificado. Esta información debe (obligatoriamente) aportarse cuando se declaran variables o constantes de tipo VECTOR. Esto puede hacerse de dos formas. Podemos introducir un subtipo intermedio y luego declarar los objetos (variables y/o constantes).

 

subtype VECTOR_5 is VECTOR(1..5); V: VECTOR_5;

 

 

 

 

 

 

o podemos declarar los objetos directamente V:VECTOR(1 .. 5);

En ambos casos los límites deben darse mediante una restricción de índices que toman la forma de un rango discreto entre paréntesis.

El índice del tipo arreglo no restringido también puede darse a través de un subtipo. Entonces, si

tenemos

 

type P is array (POSITIVE range <>) of REAL;

 

los límites reales de cualquier objeto deben estar dentro del rango estipulado por el subtipo POSITIVE.

 

Otra definición de tipo muy útil es

 

type MATRIX is array (INTEGER range <>, INTEGER range <>) of REAL; la que luego nos permite definir subtipos como

subtype MATRIX_3 is MATRIX(1 ..3, 1 .. 3);

 

y también variables

 

M:MATRIX(1 .. 3, 1 .. 3);

 

Volvamos al asunto de la asignación entre arreglos completos. Para realizar este tipo de asignación es necesario que los arreglos ubicados a ambos lados del comando de asignación sean del mismo tipo y que sus componentes puedan ser pareados. Esto no significa que los límites tengan que ser necesariamente iguales, sino sólo que la cantidad de componentes en las dimensiones correspondientes sea la misma. De ahí que podamos escribir

 

V:VECTOR(1 .. 5);

W:VECTOR(0 .. 4);

. . . V:= W;

Tanto V como W son de tipo VECTOR y tienen 5 componentes. También sería válido escribir P:MATRIX(0 .. 1, 0 .. 1);

Q: MATRIX(6 .. 7, N .. N+1);

. . . P:= Q;

 

La igualdad y diferencia entre arreglos siguen reglas similares a la asignación. Dos arreglos pueden ser comparados sólo sin son del mismo tipo, y son iguales si sus dimensiones correspondientes tienen el mismo número de componentes y si los componentes pareados son iguales.

Los atributos FIRST, LAST, LENGHT y RANGE también pueden aplicarse a tipos y subtipos en  la medida estén restringidos, entonces

 

VECTOR_6’LENGHT = 6

 

pero

 

VECTOR’LENGHT   es ilegal.

 

 

 

 

 

 

Caracteres y strings

 

Completaremos la discusión de los tipos de enumeración introduciendo los tipo caracter. En los tipos de enumeración vistos anteriormente, tal como

 

type COLOUR is (RED, AMBER, GREEN);

 

los valores habían sido representados por identificadores. También es posible tener un tipo de enumeración en el que parte o todos los valores están representados por literales de caracter.

Un literal de caracter es otra forma que puede tomar un lexema. Consiste de un caracter único encerrado entre apóstrofes. El caracter debe ser un caracter imprimible o un espacio blanco. No puede ser un caracter de control, tales como tabulador horizontal o nueva línea.

Este es un caso donde se hace distinción entre mayúsculas y minúsculas. De ahí que los literales ‘A’       y             ‘a’

sean diferentes.

 

Basados en lo dicho, podemos definir el tipo de enumeración.

 

type ROMAN_DIGIT is (‘I’, ‘V’, ‘X’, ‘L’, ‘C’, ‘D’, ‘M’);

 

y luego declarar

 

DIG: ROMAN_DIGIT:= ‘D’;

Además, son aplicables todos los atributos de los tipos de enumeración. ROMAN_DIGIT’FIRST = ‘Y’

ROMAN_DIGIT’SUCC(‘X’) = ‘L’ ROMAN_DIGIT’POS(‘M’) = 6

 

Existe un tipo de enumeración predefinido de nombre CHARACTER que es (obviamente) el tipo caracter. Su declaración es aproximadamente

 

type CHARACTER is (null, . . ., ‘A’, ‘B’, ‘C’, . . ., del);

 

Nótese que la introducción de los tipos ROMAN_DIGIT y CHARACTER lleva a la sobrecarga de algunos literales. De ahí que la expresión

 

‘X’ < ‘L’

 

sea ambigua. No sabemos si se están comparando caracteres de tipo ROMAN_DIGIT o CHARACTER. Para resolver esta ambigüedad se debe cualificar uno o ambos argumentos

 

CHARACTER’(‘X’) < ‘L’ = FALSE ROMAN_DIGIT’(‘X’) < ‘L’ = TRUE

 

Así como existe el tipo predefinido CHARACTER, existe el tipo predefinido STRING

 

type STRING is array (POSITIVE range <>) of CHARACTER;

 

Este es un tipo arreglo normal y por lo tanto sigue las reglas previamente enunciadas. Por lo tanto, podemos escribir

 

S:STRING(1 .. 7);

 

para declarar un arreglo de rango 1 .. 7. En el caso de arreglos constantes es posible deducir los límites a partir del valor inicial. Entonces, si escribimos

 

G: constant STRING:= (‘P’, ‘I’, ‘G’);

 

el límite inferior de G (es decir, G’FIRST) será 1, puesto que el tipo del índice de STRING es POSITIVE y POSITIVE’FIRST es 1.

Existe una notación alternativa más abreviada para los strings, esta consiste en una cadena de caracteres encerrada entre comilla dobles. El ejemplo anterior quedaría

 

 

 

 

G: constant STRING:= “PIG”;

 

El uso más importante de los strings es, obviamente, la creación de textos de salida. Es posible “imprimir” una secuencia simple de caracteres usando el subprograma (sobrecargado) PUT. Por ejemplo, la llamada

 

PUT(“The Countess of Lovelace”); colocará el texto

The Countess of Lovelace

 

en algún archivo apropiado.

Es posible aplicar los operadores relacionales <, <=, >, y >= sobre los string. Las reglas son las ampliamente usadas en distintos lenguajes. Por ejemplo, todas las comparaciones siguientes son verdaderas.

 

“CAT” < “DOG”

“CAT” < “CATERPILLAR” “AZZ” < “B”

“” < “A”

 

Existe, además, el operador de concatenación &, que permite unir dos strings para generar uno nuevo. El límite inferior del resultado es igual al límite inferior del primer operando. Por ejemplo, si tenemos

 

STRING_1:STRING:=“CAT”; STRING_2:STRING(11 .. 18):=“ERPILLAR”;

 

entonces

 

STRING_1 & STRING_2 =  “CATERPILLAR” STRING_2 & STRING_1 =  “ERPILLARCAT”

 

es decir, dos strings de igual largo, pero con rangos diferentes, en la primera expresión el rango es 1 .. 11 y la segunda 11 .. 21. En todo caso, cuando se están generando strings para salidas los rangos en sí no son relevantes.

Finalmente, es posible tomar sólo una parte de un string, ya sea para utilizarlo en una expresión, o para asignar ciertos valores  a una parte específica de un string. Por ejemplo, si escribimos

 

S:STRIG(1 .. 10):= “0123456789”;

. . .

T: constant STRING:=S(3 .. 8);

. . .

S(1 .. 3):= “ABC”;

 

al final T tendrá el valor “234567”, con T’FIRST = 3 y T’LAST = 8. Y S terminará con el contenido “ABC3456789”.

 

 

Ejercicio:

 

  1. Declare un arreglo constante de nombre ROMAN_TO_INTEGER que pueda ser usado como una tabla para convertir un ROMAN_DIGIT en su entero equivalente. Por ejemplo, para convertir ‘C’ en 100.

 

  1. Escriba código Ada que tome un objeto R de tipo ROMAN_INTEGER y lo calcule el valor entero correspondiente en la variable V. Debe asumirse que R es un número romano correctamente escrito.

 

 

 

 

 

 

Registros

 

Un registro es un objeto compuesto cuyos componentes tienen nombres y pueden ser de tipos distintos. A diferencia de los arreglos, no es posible tener registros de tipo anónimo. Es obligación definir un tipo registro explícito  para posteriormente crear  los objetos (constantes y/o variables) correspondientes.

Consideremos el siguiente tipo registro

 

type MONTH_NAME is (JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG,

SEP, OCT, NOV, DEC);

 

type DATE is

record

DAY:INTEGER range 1 .. 31; MONTH:MONTH_NAME; YEAR:INTEGER;

end record;

 

DATE es un tipo registro que contiene tres componentes con nombre: DAY, MONTH y YEAR. Ahora, podemos definir variables y constantes de la manera usual.

 

D:DATE;

 

declara  una  variable  de  tipos  DATE.  Mediante  la  notación  punto  se  pueden  accesar  los  componentes individuales de D. Así,  para asignar valores a estos componentes podemos escribir

 

D.DAY:= 15; D.MONTH:=AUG; D.YEAR:= 1959;

 

Los registros pueden ser manipulados como objetos completos. Además, es posible asignar valores a todos los componentes de un registro mediante un conjunto ordenado. Por ejemplo

 

D:DATE:= (15,AUG,1959); E:DATE;

. . . E:= D;

 

Es posible dar valores por omisión a todos o algunos de los componentes. Por ejemplo, al escribir

 

type COMPLEX is

record

RL: REAL:= 0.0;

IM: REAL:= 0.0;

end record;

 

se está declarando un tipo que contiene dos componentes de tipo REAL y cada uno de ellos se le da un valor por omisión de  0.0. Ahora podemos definir

 

C1:COMPLEX;

C2: COMPLEX:= (1.0, 0.0);

 

La variable C1 tiene sus componentes con los valores por omisión, es decir, 0.0. Por su parte, la variable C2 recibe valores que reemplazan a los indicados en la definición del tipo. Nótese que a pesar que el segundo componente de C2 toma el mismo valor estipulado por omisión, es necesario indicarlo explícitamente. Esto se debe a que cuando se asignan los valores mediante un conjunto, es obligación dar el valor para todos y cada uno de los componentes.

Las únicas operaciones predefinidas para los registros son = y /=, y la asignación. Es posible realizar otras operaciones a nivel de registro o definirlas explícitamente en subprogramas, como se verá más adelante.

Los componentes de un registro pueden ser de cualquier tipo; entre otros pueden ser otros registros o arreglos. Sin embargo, si el componente es un arreglo, este debe ser restringido y no puede ser de tipo anónimo. Y, obviamente, un registro no puede contener una instancia de sí mismo.

Los componentes no pueden ser constantes, pero un registro en sí si puede serlo. Por ejemplo, para representar la unidad imaginaria (raíz cuadrada de -1) podemos declarar

 

I: constant COMPLEX:= (0.0, 1.0);

 

A continuación presentamos un ejemplo más elaborado de un registro

 

 

 

 

type PERSON is

record

BIRTH: DATE; NAME:STRING (1 .. 20);

end record;

 

El registro PERSON tiene dos componentes, el primero es otros registro y el segundo es un arreglo. Ahora podemos escribir, por ejemplo

 

JOHN:PERSON; JOHN.BIRTH:=(19, AUG, 1937); JOHN.NAME(1 .. 4):=“JOHN”;

 

y tendremos

 

JOHN := ((19, AUG, 1937), “JOHN                    ”);

 

Ejercicio:

 

1. Declare tres variables C1, C2 y C3 de tipo COMPLEX; y escriba uno o más sentencias para realizar la suma y  el producto de C1 y C2, guardando el resultado en C3.





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 *