Arboles en ALGOL

Indice1. Base De Datos

2. Recursividad

3. Lista

4. Árboles binarios.

5. Variables Constantes

Estructura de datos

1. Base De Datos

Una base de datos de red esta formado por una colección de registros, los cuales están conectados entre sí por medio de enlaces.

Registro.- Es una colección de campos (atributos) Campos.- Contiene almacenado solamente un valor.

Enlace.- Asociación entre dos registros, así que podemos verla como una relación estrictamente binaria. Estructura de datos de red, abarca más que la estructura de árbol porque un nodo “hijo” en la estructura

de red puede tener más de un padre.

Diagramas de estructura de datos.

Es un esquema que representa el diseño de una base de datos de red. Este modelo se basa en representaciones entre registros por medio de ligas, existen relaciones en las que participan solo dos entidades(binarias) y relaciones en las que participan más de dos entidades (generales) ya sea con o sin atributo descriptivo en la relación.

La forma de diagramado consta de dos componentes básicos: Celdas: representan a los campos del registro.

Líneas: representan a los enlaces entre los registros.

su representación gráfica se basa en el acomodo de los campos de un  registro en un conjunto de celdas que se ligan con otro(s) registro(s)

Las estructuras de datos según la cardinalidad se representan en los siguientes casos:

Conceptos básicos.

Algoritmo.- Es un conjunto de reglas que permiten obtener un resultado determinado a partir de ciertas reglas definidas.

Algoritmo.- Es una secuencia finita de instrucciones, cada una de las cuales tiene un significado preciso

y puede ejecutarse con una cantidad finita de esfuerzo en un tiempo finito. Ha de tener las siguientes características: Legible, correcto, modular, eficiente, estructurado, no ambiguo y a ser posible se ha de desarrollar en el menor tiempo posible.

Características de un algoritmo de computador: Correcto

Legible eficiente

Diseño de algoritmos. Fases:

Diseño: se dan las especificaciones en lenguaje natural y se crea un primer modelo matemático apropiado. La solución en esta etapa es un algoritmo expresado de manera muy informal. Implementación: El programador convierte el algoritmo en código, siguiendo alguna de estas 3 metodologías.

* TOP-DOWN se alcanza el programa sustituyendo las palabras del palabras del pseudocódigo por secuencias de proposiciones cada vez mas detalladas, en un llamado refinamiento progresivo.

* BOTTON-UP parte de las herramientas mas primitivas hasta que se llega al programa.

Pruebas: Es un material que se pasa al programa para detectar posibles errores.Esto no quiere decir que el diseño no tenga errores, puede tenerlos para otros datos.

2. Recursividad

Definición.

Hablamos de recursividad, tanto en el ámbito informático como en el ámbito matemático, cuando definimos algo (un tipo de objetos, una propiedad o una operación) en función de sí mismo. La recursividad en programación es una herramienta sencilla, muy útil y potente.

Tipos.

Podemos distinguir dos tipos de recursividad:

Directa: Cuando un subprograma se llama a si mismo una o mas veces directamente. Indirecta: Cuando se definen una serie de subprogramas usándose unos a otros. Características.

Un algoritmo recursivo consta de una parte recursiva, otra iterativa o no recursiva y una condición de terminación. La parte recursiva y la condición de terminación siempre existen. En cambio la parte no recursiva puede coincidir con la condición de terminación.

Algo muy importante a tener en cuenta cuando usemos la recursividad es que es necesario asegurarnos que llega un momento en que no hacemos más llamadas recursivas. Si no se cumple esta condición el programa no parará nunca.

Ventajas e inconvenientes.

La principal ventaja es la simplicidad de comprensión y su gran potencia, favoreciendo la resolución de problemas de manera natural, sencilla y elegante; y facilidad para comprobar y convencerse de que la solución del problema es correcta.

El principal inconveniente es la ineficiencia tanto en tiempo como en memoria, dado que para permitir su uso es necesario transformar el programa recursivo en otro iterativo, que utiliza bucles y pilas para almacenar las variables.

Estructura Representación

Una tabla es una estructura homogénea en la que todos los elementos que la componen son del mismo tipo.Son estáticas, no crecen ni decrecen en tiempo de ejecución y tienen un límite preestablecido antes

de la compilación.

Para acceder a los elementos de una tabla se utilizan los “índices” y estos pueden ser de cualquier tipo escalar de PASCAL (enumerados, INTEGER, CHAR, subrango, BOOLEAN).Por ello las tablas son estructuras de acceso directo o acceso por índice.

Búsqueda secuencial.

Búsqueda secuencial con centinela.

Almacenamiento externo

Usamos espacios fuera de las de la tabla para colocar las colisiones. Dentro del almacenamiento externo hay varios tipos.

Encadenamiento directo y zona de overflow.

Encadenamiento directo.

Esta realización considera la tabla como un vector en el que cada posición contiene un elemento y un campo adicional con el comienzo de la lista de elementos con los que existe colisión.Es decir, las posibles colisiones se resuelven construyendo una lista de elementos cuya imagen hash coincida. Ventajas: eficientes y rápidos.

Inconvenientes: Para cada elemento de la lista se debe reserVAR un espacio para punteros lo que significa un desaprovechamiento de memoria en el “manejo de lista”.

Zona de Overflow.

Se reserva espacio en cierta zona de externa a la propia tabla, de aproximadamente el 10% de su tamaño, para introducir las colisiones.Cada sinónimo se almacena en la primera celda disponible de la zona de overflow.

Inconveniente: Desaprovechamiento de memoria (poco).Es poco eficiente cuando se han producido colisiones, ya que la búsqueda en la zona de overflow es secuencial.

Ventajas: Ocupa menos memoria que el anterior.El algoritmo de búsqueda y de inserción es mas sencillo.

Almacenamiento interno

Cuando el espacio usado para almacenar las colisiones esta dentro de los límites de la tabla.Dentro del almacenamiento interno están:Encadenamiento directo y encadenamiento vacío.

Encadenamiento directo.

Se usa dentro de la tabla un campo de tipo puntero para que apunte al siguiente colisionado, que estará dentro de la tabla.En ese campo se guarda la dirección del siguiente colisionado.

En el encadenamiento directo con zona de overflow podemos sobredimensionar la tabla para almacenar

las colisiones, en esta zona las casillas estarán encadenadas con una variable que apunte al primer

espacio libre de la zona de overflow.Consiste en enlazar todos los elementos cuyas claves generan igual indice primario por medio de enlaces dentro de la tabla a las nuevas posiciones ocupadas por estos elementos.

Inconvenientes: Espacio reservado en cada elemento para el enlace.

Ventajas: Más rápido que el externo con zona de overflow ya que evita la búsqueda secuencial.

Ocupación de memoria: Depende del método usado.El primer caso ocupa menos memoria, y el segundo

es más rápido.

3. Lista

Concepto.

Una lista es una estructura de datos homogénea y dinámica, que va a estar formada por una secuencia

de elementos, donde cada uno de ellos va seguido de otro o de ninguno. Homogénea: Todos los elementos que la forman tienen el mismo tipo base.

Dinámica: Puede crecer o decrecer en tiempo de ejecución según nuestras necesidades. dos listas pueden ser diferentes si:

No tienen el mismo número de elementos: L1: gato, perro.

L2: gato, canario, cerdo.

Cuando, aun teniendo el mismo número de elementos, estos son distintos: L1: gato, perro.

L2: gato, cerdo.

Cuando, aun teniendo el mismo número de elementos y siendo estos los mismos, no están dispuestos

en el mismo orden. L1: gato, perro.

L2: perro, gato.

Hay varios criterios para clasificar las listas: según su modo de acceso o según su información de acceso.

Modo De Acceso.

Atendiendo a este, se dividen en densas y enlazadas. El modo de acceso es independiente de la implementación realizada.

Listas densas

Se caracterizan porque los elementos siguen una secuencia física. Sabemos cuales es el siguiente elemento porque para acceder a él hemos tenido que pasar por todos los anteriores.

La localización de un elemento cualquiera será: El primero si es el primer elemento de la lista.

N-esimo si para llegar a el hemos pasado por N-1 elementos.

Siguen una estructura física secuencial luego se pueden implementar utilizando ficheros, ARRAYS  y punteros.

Listas enlazadas

Son aquellas en las que cada elemento que los compone contiene la información necesaria para acceder al elemento siguiente. La localización de un elemento cualquiera será:

Un elemento de la lista tendrá la dirección K si K es el primero y K es conocido (dirección de inicio). Estará en la dir. J si J está contenida en el elemento anterior.

Informacion de acceso. Listas ordinales

Los elementos se van colocando en la lista a medida que llegan y se identifican por el orden de llegada.El acceso a un elemento es por su orden o posición relativa dentro de la lista.

Listas calificadas

Los elementos se clasifican por una clave y pueden estar ordenados o no estarlo. A un elemento se accede por la información contenida en un campo clave.

Diferencias: En la primera clase importa en orden de llegada, mientras que en la segunda depende de la clave.

Pilas.

Una pila es una lista ordinal en la que el modo de acceso a sus elementos es del tipo LIFO. Los

añadidos y extracciones de elementos de una estructura se realizan solo por un extremo, luego el único elemento accesible de la pila es el que se encuentre en la cima. Esto exigirá que la manipulación sobre

un elemento, necesite que el mismo ocupe la posición de cima.

Sobre una estructura de tipo pila, surgen de forma natural las operaciones que permiten añadir elementos y quitar elementos.

Implementación utilizando tablas

Esta realización consiste en ir guardando consecutivamente los elementos de la pila en un vector de tamaño fijo. Un índice marcará la posición del último elemento que se ha añadido a la pila. Por tanto, las inserciones en la estructura se realizarán en la posición inmediatamente siguiente a la posición marcada como cima, pasando a ser esta nueva posición ocupada la nueva cima de la pila.

El hecho de utilizar un vector para almacenar los elementos, puede conducir a la situación en que la pila esté llena, es decir, que no quepa ningún elemento más. Esto se producirá cuando el índice que señala

la cima de la pila sea igual al tamaño del vector.

Otros Tipos De Listas

Listas reorganizables.- Son aquellas listas en las que el último elemento consultado se sitúa al principio. Listas circulares.- En ellas el último elemento apunta al primero.

Listas doblemente enlazadas.- Cada elemento tiene dos punteros, uno de los cuales apunta al elemento siguiente y otro al anterior.

Listas circulares doblemente enlazadas

4. Árboles binarios.

Los árboles de grado 2 tienen una especial importancia. Se les conoce con el nombre de árboles

binarios. Se define un árbol binario como un conjunto finito de elementos (nodos) que bien está vació o está formado por una raíz con dos árboles binarios disjuntos, llamados subárbol izquierdo y derecho de

la raíz.

En los apartados que siguen se considerarán únicamente árboles binarios y, por lo tanto, se utilizará la palabra árbol para referirse a árbol binario. Los árboles de grado superior a 2 reciben el nombre de árboles multicamino.

Árbol binario de búsqueda.- Los árboles binarios se utilizan frecuentemente para representar conjuntos

de datos cuyos elementos se identifican por una clave única. Si el árbol está organizado de tal manera que la clave de cada nodo es mayor que todas las claves su subárbol izquierdo, y menor que todas las claves del subárbol derecho se dice que este árbol es un árbol binario de búsqueda.

Ejemplo:

Operaciones básicas.- Una tarea muy común a realizar con un árbol es ejecutar una determinada operación con cada uno de los elementos del árbol.Esta operación se considera entonces como un parámetro de una taré más general que es la visita de todos los nodos o, como se denomina usualmente, del recorrido del árbol.

Si se considera la tarea como un proceso secuencial, entonces los nodos individuales se visitan en un orden específico, y pueden considerarse como organizados según una estructura lineal. De hecho, se simplifica considerablemente la descripción de muchos algoritmos si puede hablarse del proceso del siguiente elemento en el árbol, según un cierto orden subyacente.

Hay dos formas básicas de recorrer un árbol: El recorrido en amplitud y el recorrido en  profundidad. Recorrido en amplitud.- Es aquel recorrido que recorre el árbol por niveles, en el último ejemplo sería:

12 - 8,17 - 5,9,15

Recorrido en   profundidad.- Recorre el árbol por subárboles. Hay tres formas: Preorden,  orden central y postorden.

Preorden: Raiz, subárbol izquierdo y subárbol derecho. Orden central: Subarbol izquierdo, raiz, subarbol derecho. Postorden: Subarbol izquierdo, subarbol derecho, raiz. Ejemplo:

Preorden:  20 - 12 - 5 - 2 - 7 - 13 - 15 - 40 - 30 - 35 - 47

Orden central:  2 - 5 - 7 - 12 - 13 - 15 - 20 - 30 - 35 - 40 - 47

Postorden:  2 - 7 - 5 - 15 - 13 - 12 - 35 - 30 - 47 - 40 - 20

Ejemplo:

Preorden:  / + a b * c d Notación polaca

Orden central: a + b / c * d Notación infija

Postorden:  a b + c d * / Notación polaca inversa

Estructura de datos

Variables

Las variables son estructura de datos usados para almacenar información. Hay dos tipos de información que puede ser almacenada: Números y texto. Antes de usar una variable ésta, deberá primero ser

definida:

Dim nombre_de_variable As Tipo

Ejemplo:

Dim  precio  As Long

Dim nombre_de_articulo As String

Tipo                 Rango permitido

Integer             -32,768 a 32,767

Long                -2,147,483,648 a 2,147,483,647

Single

Double

-3.402823E38 a -1.401298E-45

1.401298E-45 a 3.402823E38

-1.79769313486232D308 a -4.94065645841247D-324

4.94065645841247D-324 a 1.79769313486232D308

Currency          -922337203685477.5808 a 922337203685477.5807

String              0 a 65,000 bytes

Valores de fechas: 1/1/0000 a 12/32/9999

Variant

Numérico: igual que Double

Texto: Igual que String

Si una nueva variable es declarada sin especificación VB por default la deberá

tomar como tipo Variant

Una vez que una variable se ha creado, se le puede asignar un valor. Para esto se usa el operador ‘ = ‘.

En el primer ejemplo de abajo a una variable se le asigna un valor constante, mientras que en el segundo se le asigna el contenido de una variable multiplicada por 10.

Ejemplo 1: precio = 29.95

Ejemplo 2: precio_total = precio * 10

El Alcance de una variable es definido como su rango de operación. Hay tres tipos de alcance en una variable:

1.   Local - La variable solo puede ser usada en el procedimiento actual ( use Dim dentro del procedimiento requerido).

2.   Módulo - La variables pueden ser accesadas desde cualquier procedieminento de la forma actual (use Dim dentro de la sección de Declaraciones Generales de la forma).

3.   Global - Pueden ser accesados desde cualquier procedimiento y desde cualquier forma. (usa

Global dentro de la sección de Declaraciones Generales de un módulo).

Variables Estáticas

El declarar variables y arreglos como local en un procedimieneto/función es muy usado, porque esto minimíza los efectos extraños que pueden ocurrir cuando se usan variables globales. Sin embargo, cuando usamos una variable local en un procedimiento VB crea un espacio de memoria para mantener

el valor de esta variable , esto sucede cuando lee el estatuto Dim, pero cuando llega al final del procedimiento (End Sub) VB libera el espacio asigndo para el valor de la variable local. Agrega el siguiente código a un botón de comando y observa que valores son impresos:

Sub Command1_Click ()

Dim numero As Integer  ‘ Crea una variable Local normal numero  = numero + 1

Print numero

End Sub

Después de dar clic varias veces al botón de comando deberás ver una columna de unos en el lado izquierdo de la forma. El valor nunca será arriba de uno a pesar de que el valor de la variable se incrementa en uno cada vez. Esto es porque cada vez que el procediemineto es llamado, haciendo clic

en el botón, VB esta trabajan con una variable diferente. Esta tiene exactamente el mismo nombre en el programa pero es una variable completamente diferente. Para que esto no suceda así, introduce

Staticen el lugar de Dim: Sub Command1_Click ()

Static numero As Integer ‘ Crea una variable estática local numero = numero + 1

Print numero

End Sub

Ahora en vez de que el valor de la variable se pierda cuando el procedimiento termina, con este cambio

(static) su valor permanecerá hasta que todo el programa termine. De esta manera, podemos ver una lista de números que se incrementan en uno cada vez que se le da clic al botón de comando.

Nota: La nueva variable estática es una variable de alcance local, si cualquier procedimiento trata de accesar esta variable no prodrá lograrlo. Agrega a la forma un nuevo botón de comando, el cual deberá tener un nuevo procedimiento ‘Click’, y trata de corregir o imprimir el valor de la variable estática que contiene el primer botón.

El contenido de un arreglo local, también puede mantenerse mientras el programa se ejecute. Para hacer esto agrega el estatuto ‘Static’ en lugar de ‘Dim’ como lo hicimos en el ejemplo de arriba.

Static salarios(199) As Long

5. Variables Constantes

Las constantes son similares a una variable pero tienen un valor determinado que se mantiene igual en toda la ejecución del programa. El contenido de una varible puede cambiar tantas veces sea necesario.

¿Porque usar una constante si no puede cambiar de valor?. Hacemos esto cuando deseamos usar un mismo número o una palabra (string) varias veces. Por ejemplo, en un programa para calcular los impuestos de todo el año, deberá hacer referencia a un valor en varias partes del programa, que puede

ser el por ciento de impuesto mensual con respecto a las ganancias. Par ello podemos usar una variable llamada IMP, que mantendrá el valor en el evento Form_Load.

En el siguiente ejemplo definimos una contante llamada ‘ IMP ‘ y le asignamos el valor de 1.175. Ese es usado en estatuto Print con la variable pago_total para calcular la cantidad total a pagar. Note que en

lugar de escribir 1.175 en la fórmula nos referimos a el nombre de la constante. Ejemplo:

Const IMP = 1.175           ‘ Declara y asigna un valor  a la constante

Dim pago_total As Currency   ‘ Declara una variable local para almacenar el total a pagar pago_total = 560.95

Print “Total = “; pago_total * IMP

Como las variables las constantes también tiene reglas de alcance. Hay constantes globales que

pueden ser accesadas por cualquier módulo o cualquier forma del proyecto, las constantes de módulo solo son accesadas por la foma que los contiene, y las contantes locales son accesadas solamente por

el objeto actual o procedimiento/función.

1.   Local - usa ‘Const’ dentro del procedimiento requerido.

2.   Módulo - usa ‘Const’ dentro de la sección deDeclaraciones Generales de una forma o módulo.

3.   Global - usa ‘Global Const’ dentro de la sección deDeclaraciones Generales de un módulo (ésto

es Module1.bas).

Arreglos (arrays)

La variables son muy usadas para lamacenar pequeñas cantidades de información, pero no son convenientes para grandes cantidades de información muy similar. Por ejemplo, para almacenar los salarios de doscientos empleados, necesitaremos 200 variables diferentes. Una mejor forma de almacenar esta información será usra una estructura de datos llamada arreglos array.

Un arreglo es similar a las celdas en un panal de abejas. Todo el arreglo tiene un nombre, y cada celda tiene una dirección. Para el problema de los salarios planteado arriba, un arreglo el cual tiene 200

elementos (celdas) , usaremos el comando Dim para cerar un nueva variable, pero marcaremos también

el tamaño de esta variable .

Dim nombre_del_arreglo (tamaño) [As Tipo] Ejemplo: Dim salarios(199) As Long

En el ejemplo de arriba creamos un arreglo con 200 elementos. El tamaño marcado es de 199 porque por default VB empieza la numeración con 0.

Si sabemos que el nombre ‘Jaime ‘ es el empleado número 24 y tiene un salario de 25,000 pesos mensuales, podemos lintroducir esta cantidad en el arreglo de la siguiente forma:

salarios(23) = 25000

Contrario a lo anterior, si deseamos saber cula es el salario del empleado número 189, podemos usar:

lblvalor.Caption = salarios(188)

Nota: En los dos ejemplos anteriores, para accesar un elemento es necesario colocar el número del elemento anterior (si deseamos el 150, pedimos el 149). esto es VB empieza un arreglo de 0, no de 1.

Sin embargo VB pude ser forzado a empezar con 1, agregando el estatuto ‘Option Base 1′ en la sección

de declaraciones generales de la forma o el módulo.

Trabajo enviado por:

Ist  Cepea  Lima - Perú Bermúdez  Moncada,  Isabel Chumpitaz  Heirisman, Roel germany0116@yahoo.es Guevara  Retamozo  Miguel

Salazar Beaumont Blanco, Enrique

Guía Practica de ADA

 

Los contenidos y organización de esta guía están realizados en función de las necesidades específicas de las asignaturas de Metodología de la Programación y Metodología de la Programación I de la Ingeniería en Informática y las Ingenierías Técnicas en Informática de Gestión y de Sistemas que se imparten en la Facultad de Informática y en la Escuela Universitaria de Informática por parte del Departamento de  Informática y Sistemas.


Estructura básica de un programa en Ada.
Variables, constantes y tipos.
Declaración de variables
Definición de constantes
Definición de tipos
Tipos escalares.
Enumerados
Enteros
Operaciones con tipos ordinales
Reales
Sentencias, asignación y expresiones.
Entrada/salida.
Sentencias de control.
Subprogramas.
Tipos estructurados.
Ristras de caracteres.
Excepciones.
Ficheros.
Ficheros de texto
Ficheros uniformes
Memoria dinámica.
Estructura general de un programa en Ada.
Packages.
Tipos private y limited private.
Unidades genéricas.
Parámetrización de unidades genéricas.


APÉNDICES
Entorno de programación
Operadores
Palabras reservadas
Funciones matemáticas
Conversión de tipos
Depuración

Estructura básica de un programa en Ada.

Un programa en Ada consta de al menos dos partes: una cláusula de contexto y un procedimiento principal.

–Programa simple en Ada
with Text_IO; –cláusula de contexto

procedure Hola is –procedimiento principal
    –Parte de declaraciones
begin
    –Cuerpo de sentencias ejecutables
    Text_IO.Put_Line(”Hola”);
end Hola;

(Las líneas a partir de los dobles guiones,”–”, son comentarios. Todas las sentencias en Ada deben terminar con un punto y coma (”;”))

Las cláusulas de contexto sirven para especificar el uso de librerías externas al programa. Una librería es un conjunto de recursos (procedimientos, funciones, tipos, …) ya desarrollados y compilados, y que se hallan disponibles para ser usados por nuestros programas. En el caso del ejemplo, la cláusula “with” de la 2ª línea está indicando que nuestro programa va a hacer uso de la librería “Text_IO”, la cual proporciona servicios básicos de entrada/salida; concretamente, en el ejemplo se está usando el procedimiento “Put_Line”, que saca un mensaje por pantalla, y se especifica que hay que buscarlo en la librería Text_IO, antecediendo al nombre del procedimiento con el nombre de la librería seguido por un punto (”.”). Si se quiere evitar tener que hacer este tipo de cualificaciones con todos los recursos externos que se empleen, se habrá de disponer una cláusula “use” como en el siguiente ejemplo:

–Programa simple en Ada
with Text_IO; –cláusula de contexto
use Text_IO;
procedure Hola is –procedimiento principal
    –Parte de declaraciones
begin
    –Cuerpo de sentencias ejecutables
    Put_Line(”Hola”); –ya no es necesario poner “Text_IO.”
end Hola;

El procedimiento principal es el punto de arranque del programa, del que dependen en última instancia, todos los demás procedimientos y funciones que se utilicen. Un procedimento consta de: (1) una cabecera (línea 5 del segundo ejemplo), donde se le da nombre, (2) una sección de declaraciones, donde se declaran y/o definen todos los elementos que el procedimiento va a utilizar (variables, constantes, tipos, procedimientos, funciones, …), y (3) el cuerpo del procedimiento –las sentencias que describen su algoritmo, líneas 7 a 10–, que comienza con la palabra “begin” y termina con la palabra “end” acompañada del nombre del procedimiento. El cuerpo de un procedimiento no puede estar vacío, en caso de desear, circunstancialmente, tener un procedimiento que no haga nada, debe de contener la instrucción “null;”.

Variables, constantes y tipos.

Declaración de variables.

Las variables se declaran como “nombre_de_variable : tipo“, tal como en el siguiente ejemplo:

x : integer; –una variable de tipo integer llamada x
Si hay que declarar varias variables del mismo tipo, se puede formar una lista, aunque ello no es obligatorio:

x, y, z : integer;
Las variables pueden inicializarse con un valor en el momento de su declaración:

x, y, z : integer := 0;

Definición de constantes.

Para definir una constante, se hace igual que para declarar una variable inicializándola al mismo tiempo, pero poniendo la palabra “constant” antes del tipo.

c: constant integer := 10;
Ahora bien, si se trata de constantes numéricas, también se pueden definir sin especificar su tipo:

pi: constant := 3.1416;

Definición de tipos.

Ada, como todos los lenguajes, ofrece un conjunto básico de tipos predefinidos junto con mecanismos para definir nuevos tipos. Para definir un nuevo tipo, se usa la palabra “type”, seguida del nombre del nuevo tipo; la palabra “is”, y la descripción del nuevo tipo:

type byte is range 0..255;
Ada, también ofrece la posibilidad de definir un subtipo de un tipo ya definido. Por ejemplo:

subtype byte is integer range 0..255;
La diferencia entre ambas definciones de byte, es que en la segunda, al ser un subtipo, es una versión restringida el tipo base de la definición (en este caso, integer), mientras que en la primera se está definiendo un tipo distinto, que no es compatible con el integer, a pesar de que su rango de valores parezca coincidir con una parte de los enteros. De hecho, se puede declarar un nuevo tipo para que sea en todo igual a otro existente, y sin embargo incompatible:

type MiEntero is new integer;
type MiEnteroCorto is new integer range 0..255;

Tipos escalares.

Los tipos en Ada se clasifican en escalares (simples) y estructurados. Los escalares se dividen en: discretos, u ordinales, y no discretos. Los tipos discretos son enumerados y enteros. Los no discretos son los reales. Tanto los enteros como los reales se clasifican a su vez como numéricos.

Los tipos enumerados son aquellos que se definen especificando la lista ordenada de valores que lo componen:

type Día is (Lun, Mar, Mie, Jue, Vie, Sab, Dom);
type Palos is (Oros, Bastos, Espadas, Copas);
type Género is (M, F);
type Color is (Blanco, Rojo, Amarillo, Verde, Azul, Marrón, Negro);
type Luz is (Rojo, Naranja, Verde);
Una vez definido el tipo, se pueden declarar variables. Si, como ocurre con los tipos Color y Luz, hay valores que se repiten, habrá que cualificar ese valor cuando su uso pueda ser ambiguo:

x := Color’Rojo; — x debe ser de tipo Color
y := Luz’Rojo; — y debe ser de tipo Luz
Ada tiene predefinido el tipo enumerado character –que representa el alfabeto de caracteres utilizado–, y el tipo boolean, que puede tomar los valores true o false.

Ada posee un tipo integer predefinido cuyo rango de valores depende de la implementación del lenguaje. Además, se pueden definir nuevos tipos enteros que pueden ser, con signo o sin signo:
type MiEntero is range -32768..32767; –Entero con signo
type MiNatural is mod 32767; –Entero sin signo (rango 0..32766)

En ambos casos se pueden usar expresiones para definir los límites del tipo:
type MiEntero is range -2**15..2**15; –Entero con signo
type MiNatural is mod 2**16; –Entero sin signo (rango 0..65535)

Existen predefinidos en el paquete standard dos subtipos del tipo integer:
subtype Natural is integer range 0..Integer’last;
subtype Positive is integer range 1..Integer’last;

Todos los tipos ordinales tienen asociadas, entre otras, las siguientes operaciones como atributos (todas se usan cualificadas con el nombre del tipo), sea T un tipo ordinal:

T’succ(x) devuelve el valor que sigue a x en la lista de valores del tipo (el último valor no tiene sucesor)

T’pred(x) devuelve el valor que precede a x en la lista de valores del tipo(el primer valor no tiene predecesor)

T’pos(x) devuelve la posición que ocupa x en la lista de valores del tipo (al primer valor le corresponde la posición 0)

T’val(x) devuelve el valor correspondiente a la posición x en la lista de valores del tipo

T’first devuelve el valor más pequeño del tipo

T’last devuelve el mayor valor del tipo

T’range devuelve el número de valores posibles del tipo

Ejemplos:

x:=Día’succ(Mar); –x toma el valor Mie

x:=Día’pred(Mar); –x toma el valor Lun

y:=Día’Pos(Mar); –y toma el valor 1

x:=Día’Val(3); –x toma el valor Jue
Ada también tiene un tipo predefinido, float, para representar números reales, cuyo rango y precisión dependen de la implementación. No obstante, el programador también puede definir sus propios reales con dos posibles formatos: coma fija y coma flotante.

Al definir un tipo real de coma flotante se puede especificar el número de dígitos significativos, y el rango de valores posibles.

type rflotante is digits 5 range -1.0..1.0;
Al definir un tipo real de coma fija, se pretende obtener una precisión definida. Los números reales en coma fija se pueden catalogar en: ordinarios y decimales. En el primer caso, se especifica el límite de error en su representación (delta) y, si se quiere, el rango de variación. En el segundo se indica el número de dígitos significativos y el incremento entre valores sucesivos admitidos (delta).

type rfija1 is delta 0.01 range -1.0..1.0; –Coma fija ordinario
type rfija2 is delta 0.01 digits 5;  –Coma fija decimal

En el caso de números decimales en coma fija, el delta tiene que ser potencia de 10.

Sentencias, asignación y expresiones.

Toda sentencia en Ada termina en punto y coma (”;”). Existe la sentencia nula (que no hace nada), y se expresa como “null;”.

La asignación se expresa con el símbolo “:=”. Ejemplo:

x := 3;
En la parte izquierda de la asignación debe ir una variable. En la parte derecha de la asignación va una expresión de un tipo adecuado.

Una expresión está formada por operandos (constantes, variables, expresiones, …) y operadores. Como resultado de su evaluación una expresión da lugar a un valor. El tipo de la expresión es el tipo del valor resultante. Ejemplos:

-4.0
-4.0 + A
B**2 - 4.0*A*C
Index = 0 or Item_Hit
(Cold and Sunny) or Warm
A**(B**C)

Entrada/salida.

Las funciones básicas de entrada/salida son:
get(X), put(X) para X de tipo character, string, integer o float
get_line(S,L), put_line(S) para S de tipo string. L es la longitud leída. Leen o escriben una string en una línea y pasan a la siguiente.
new_line, que, cuando se está escribiendo, inicia una nueva línea.
skip_line, que, cuando se está leyendo, desestima el resto de la línea actual.

Para realizar entrada/salida de ristras o caracteres basta con incluir la cláusula de contexto “with Text_IO;”. Para realizar entrada/salida de otros tipos escalares hay que hacer, además, una declaración, en la sección de declaraciones del procedimiento, que especifica la aplicación de un paquete genérico de entrada/salida adecuado sobre el tipo en cuestión (a esto se llama crear una instancia del paquete), tal como se muestra en los siguientes ejemplos:

package Día_IO is new Enumeration_IO(Día); –Enumeration_IO sirve para tipos enumerados
package MiEntero_IO is new Integer_IO(MiEntero);–Integer_IO sirve para enteros con signo
package rf_IO is new Float_IO(rf); –Float_IO sirve para reales en coma flotante
package rfija1_IO is new Fixed_IO(rfija1);–Fixed_IO sirve para reales en coma fija ordinarios
package rfija2_IO is new Decimal_IO(rfija1);–Decimal_IO sirve para reales en coma fija decimales
package Natural_IO is new Modular_IO(Natural);–Modular_IO sirve para enteros sin signo
En cualquier caso, hay que utilizar la cláusula “with Text_IO;”, puesto que estos paquetes de entrada/salida (Enumeration_IO, Integer_IO, Float_IO, …), se encuentran incluidos en la librería “Text_IO”.

Sentencias de control.

Selección de dos alternativas.

if Exp_lógica then
sentencias;
end if;
if Exp_lógica then
sentencias;
else
sentencias;
end if;

Selección múltiple.

case selector is
 when alternativa => sentencias;
 when alternativa => sentencias;

 when others => sentencias;
end case;

El selector debe ser una expresión discreta de tipos integer o enumerados (tipo ordinal). Las alternativas pueden ser uno o varios valores, o rangos, del tipo del selector separados por “|” (equivale al operador OR).

case mes is
 when 1 .. 2 | 12 => put(”El invierno es duro”);
 when 3 .. 5 => put(”Primavera de la vida”);
 when 6 .. 8 => put(”Estación llena de diversión”);
 when 9 .. 11 => put(”Época de reflexión”);
 when others => put(”¿En qué planeta estás?”);
end case;

Los valores no pueden repetirse entre dos cláusulas “when”. En el caso de que las cláusulas “when” no cubran todos los posibles valores del tipo del selector, es necesario incluir la cláusula “others” para los valores no contemplados.

Iteración.

Controlada por contador.

for variable in [reverse] secuencia de control loop

end loop;

La variable de control es local al bucle, NO SE DECLARA (es del tipo de la secuencia de control) y no puede modificarse explícitamente. La secuencia de control puede ser un rango de un tipo ordinal (entero o enumerado). Cuando se utiliza “reverse” la secuencia de control se recorre en orden inverso:

for num in reverse 1..5 loop
Put(num); — escribe 5 4 3 2 1
end loop;
Controlada por condición lógica.

while condición loop

end loop;
Bucles sin esquema de iteración.

En Ada se puede construir un bucle tal como:

loop

end loop;

del que se sale, normalmente, mediante una sentencia “exit when” o con una alternativa que contenga una cláusula “exit”.

loop

    exit when condición;

end loop;

Bloques.

Un bloque es una sentencia compuesta, formada por una secuencia de sentencias agrupadas mediante las palabras delimitadoras “begin”, “end”, y posiblemente acompañadas de algunas declaraciones locales.
[declare
    declaraciones locales]
begin
    sentencias
end;

(la parte delimitada por corchetes es opcional)

Un bloque puede ponerse en cualquier sitio donde pueda ponerse una sentencia simple.

Ejemplos de bloques:
–bloque sin declaraciones locales
begin
    Put_Line(”Hola”);
end;

–bloque con declaraciones locales
declare
    Aux : integer; –la variable Aux sólo existe dentro del bloque
begin
    Aux := i; –i, j están declarados en un ámbito más externo
    i := j;
    j := Aux;
end;

Subprogramas.

Definición de subprogramas.

Un subprograma es un procedimiento o función. La estructura de la definición de un subprograma consta de tres elementos:

  1. Cabecera
  2. Declaraciones locales
  3. Sentencias ejecutables

La cabecera describe el protocolo del subprograma, y consta de: especificación de “procedure” o “function”, nombre del subprograma, lista de parámetros y tipo del resultado, caso de ser una function.

procedure Nombre_procedimiento (parámetros) is

function Nombre_función (parámetros) return Tipo is

La lista de parámetros está formada por especificaciones de parámetros formales separadas por “;”. Cada especificación de parámetros consta de una lista de nombres de parámetros separados por “,” y seguida de “:”, el modo de paso (in, out o in out) y el tipo de los parámetros.

procedure LisParam(S1,S2: in integer;S3: out float) is

  • Los parámetros “in” son constantes y no pueden modificarse en el subprograma.
  • Los parámetros “in out” pueden usarse y modificarse.
  • Las funciones sólo tienen parámetros “in”.

La parte de sentencias ejecutables comienza con la palabra “begin” y abarca hasta que aparezca la palabra “end” seguida del nombre del subprograma y “;”.

La parte de declaraciones locales abarca desde la cabecera hasta la palabra “begin” que da comienzo a las sentencias ejecutables. Los elementos declarados (variables, tipos, subtipos, procedimientos,…) tienen ámbito estático.

–Ejemplo de subprogramas
with Text_IO; use Text_IO;
procedure Ejemplo is –procedimiento principal Ejemplo
–requerimientos de entrada/salida;
package Sal1 is new Integer_IO(integer);
package Sal2 is new Float_IO(float);
use Sal1, Sal2;
–función local EsPar
  function EsPar(x: in integer) return boolean is
  begin
      return ((x rem 2) = 0);
  end EsPar;
–procedimiento local Media
  procedure Media(x,y: in float;med: out float) is
      dos: float; –variable local
  begin
      dos := 2.0;
      med := (x + y) / dos;
  end Media;

R1,R2,R3 : float; –variables locales de Ejemplo
i : integer;
begin
put(”Entre un numero: “);
Get(i); skip_line;
if EsPar(i) then put_line(”es par”); else put_line(”no es par”); end if;
put(”Entre una pareja de numeros separados por un espacio: “);
Get(R1); Get(R2); skip_line;
Media(R1,R2,R3);
Put(R3); new_line;
end Ejemplo;

Sobrecarga de operadores.

Ada permite que el programador sobrecargue los operadores del lenguaje, esto es, que pueda redefinirlos dándoles nuevos significados. Para sobrecargar un operador, simplemente hay que definir una función cuyo nombre sea el operador entre comillas y que tenga los parámetros adecuados. Por ejemplo, dado el siguiente tipo:
type Complejo is record
    PReal, PImag: float;
end Complejo;

podemos sobrecargar el operador de suma (”+”) para utilizarlo con el fin de sumar números complejos:

function “+”(A, B : in Complejo) return Complejo is
    Suma: Complejo;
begin
    Suma.PReal := A.PReal + B.PReal;
    Suma.PImag := A.PImag + B.PImag;
    return Suma;
end “+”;

Una vez definida esta función, se puede aplicar el operador entre variables del tipo definido en los parámetros, devolviendo un valor del tipo definido como resultado.

S, X, Y: Complejo;

S := X + Y;

Sólo se pueden redefinir los operadores del lenguaje (no se pueden inventar operadores), y siempre manteniendo su cardinalidad (los binarios se redefinirán con funciones de dos parámetros y los unarios con funciones de un parámetro).

Tipos estructurados.

Arrays.

Los arrays son estructuras homogéneas, es decir, están formados por un conjunto ordenado de elementos del mismo tipo que se pueden acceder individualmente mediante índices discretos.

En Ada hay dos clases de arrays: restringidos y no restringidos. Los restringidos son arrays para los que se especifica el tamaño en su definición (tamaño fijo). Los no restringidos son aquellos en los que el rango de los índices no se establece al definir el tipo, sino que se concreta posteriormente.

type Vector is array(1..10) of float; –array restringido de 10 elementos
type Libre is array(integer range <>) of float; –array no restringido
V: Vector;
L: Libre(1..10);

Si el array tiene más de una dimensión, se declara separando los rangos de cada dimensión mediante comas (”,”).
type Matriz is array(1..5,1..3) of integer;–matriz de 5×3

A: Matriz;

Los arrays tienen los siguientes atributos: First, Last, Range, Length. Si A es un array, entonces:

A’First es el límite inferior del rango del primer índice de A.
A’First(N) es el límite inferior del rango del N-ésimo índice de A
A’Last es el límite superior del rango del primer índice de A.
A’Last(N) es el límite superior del rango del N-ésimo índice de A
A’Range es equivalente al rango A’First .. A’Last.
A’Range(N) es equivalente al rango A’First(N) .. A’Last(N).
A’Length es el número de valores del rango del primer índice.
A’Length(N) es el número de valores del rango del N-ésimo índice.


–Ejemplo que recorre una matriz escribiendo sus elementos
for i in A’Range(1) loop
   for j in A’First(2)..A’Last(2) loop
      put(A(i,j)); put(” “);
   end loop;
   new_line;
end loop;

Literales arrays e inicialización de arrays.

Se puede expresar un valor literal de tipo array utilizando paréntesis para agrupar los valores por filas y columnas. Se puede utilizar la palabra “others” para indicar un valor por defecto para las posiciones que no se especifiquen explícitamente. Un literal de tipo array se puede utilizar, entre otras cosas, para inicializar una variable de tipo array en su declaración.

    type matriz is array(1..5,1..5) of integer;
    type vector is array(1..5) of integer;

    v1 : vector := (1,2,3,4,5);
    v2 : vector := (9,8,7, others => 0);
    v3 : vector := (1 => 9, 3 => 8, 5 => 7, others => 0);

    m1 : matriz := (others => (others => 0));
    m2 : matriz := ((1,2,3,4,5),
                    (1,2,others => 0),
                    (1 => 9, 3 => 8, 5 => 7, others => 0),
                   others => (others => 0));

Rodajas (slices).

Se puede hacer referencia a un trozo de un array monodimensional, simplemente especificando sus límites. Este tipo de “corte” se conoce como rodaja o slice.

 v1(1..3):= v2(3..5);

Records.

Los records son estructuras heterogéneas, es decir, agregados de elementos del mismo o distintos tipos que se pueden acceder individualmente mediante su nombre.

Un record se define con la palabra “record”, seguida de la declaración de los campos del record y “end record”.

type Complejo is record
real: float;
imag: float;
end record;

Para acceder a una campo individual de un record se utiliza el operador punto (”.”); mediante el nombre de la variable cualificada con el nombre del campo en cuestión:

x,w : Complejo; –Declaración de variables de tipo record
y   : array (1..20) of Complejo; –Declaración anónima de un array

x.real := 3.0;
x.imag := x.real;
y(7) := x;

y(1).real := 3.0;

Literales records e inicialización de records.

Se pueden formar literales records de dos formas:

  • como agregado posicional, especificando los valores de todos los campos en el orden adecuado y entre paréntesis

    x := (3.5, 7.1);

  •  como agregado nominal, especificando los nombres de los campos junto con los valores

    w := (real => 3.5, imag => 7.1);
    w := (imag => 7.1, real => 3.5);

Ristras de caracteres.

Ada ofrece los tres tipos posibles de ristras de caracteres: tamaño fijo, tamaño limitado y tamaño dinámico.

Cláusula de contexto

Para usar ristras de tamaño fijo, tamaño limitado y tamaño dinámico, hay que incluir en la cláusula “with” los paquetes “Ada.Strings.Fixed”, “ada.Strings.Bounded” y “Ada.Strings.Unbounded”, respectivamente.
    with Text_IO, Ada.Strings.Fixed,
         Ada.Strings.Bounded, Ada.Strings.Unbounded;

En la cáusula “use” sólo hay que incluir “Ada.Strings.Fixed” y “Ada.Strings.Unbounded”, ya que las ristras de tamaño limitado (bounded) necesitan una instanciación para fijar la longitud máxima antes de poder usarse.
    use Text_IO, Ada.Strings.Fixed, Ada.Strings.Unbounded;

Declaración

Una ristra de tamaño fijo es básicamente un array de caracteres que se declara con la palabra “String” y especificando el rango de variación de los índices, que debe ser de tipo Positive. (Lo más normal es que este rango empiece en 1).
    sf1, sf2 : String(1..10); –variables de tipo ristra de 10 caracteres

Antes de declarar ristras de tamaño limitado es necesario instanciar el subpaquete genérico llamado “Ada.Strings.Bounded.Generic_bounded_length” para el tamaño máximo que se desee (esto se hace para poder tener instancias para distintas longitudes).
    package String_max_10 is new Ada.Strings.Bounded.Generic_bounded_length(10);
    use String_max_10;

Una vez hecho esto se pueden declarar variables de tamaño limitado (tipo “Bounded_string”), cualificando el nombre del tipo con el de la instancia definida.
    sl1, sl2 : String_max_10.Bounded_string; –ristras de hasta 10 caracteres

Las ristras de longitud dinámica se declaran del tipo “Unbounded_string”.
    sd1, sd2 : Unbounded_string; –ristra de longitud dinámica

Ristra nula

En las ristras de tamaño fijo no existe la ristra nula, dado que una variable de tipo “String” siempre tiene la longitud definida (Ada utiliza caracteres de relleno, por defecto es el espacio).

La ristra nula para las ristras de tamaño limitado está representada por el valor “Null_Bounded_String”.
    sl1 := Null_Bounded_String.

La ristra nula para las ristras de tamaño dinámico está representada por el valor “Null_Unbounded_String”.
    sd1 := Null_Unbounded_String;

También sirve para representar la ristra nula una ristra fija vacía, que se representa mediante dos dobles comillas (”"), pero para ello son necesarias las funciones de coversión entre ristras.

Literales ristra

Un valor de tipo ristra se escribe como una secuencia de caracteres delimitada por comillas dobles: “esto es una ristra”. Un valor literal es una ristra de tamaño fijo cuyo tamaño es igual al número de caracteres de que consta (18 en el ejemplo). Los literales de tipo “Character” se delimitan por comillas simples.
    “a”   esto es una ristra.
    ‘a’  esto es un carácter, no es una ristra.

Conversión entre ristras

En los respectivos paquetes, existen funciones para convertir ristras de tamaño fijo a ristras de tamaño limitado o dinámico y viceversa.
    sl1 := to_bounded_string(”esta linea”);  conversión de string a bounded-string
    sf1 := to_string(sl1);                       –conversión de bounded_string a string
    sd1 := to_unbounded_string(”esta linea”);conversión de string a unbounded_string
    sf2 := to_string(sd1);                       –conversión de unbounded_string a string

La conversión entre ristras limitadas y dinámicas ha de hacerse a través de ristras de tamaño fijo.
    sd2 := to_unbounded_string(to_string(sl1));conversión limitada->fija->dinámica
    sl2 := to_bounded_string(to_string(sd1));  –conversión dinámica->fija->limitada

Se puede asignar la ristra nula convirtiendo una ristra fija vacía.
    sl1 := to_bounded_string(”");   –asigna la ristra nula a sl1
    sd1 := to_unbounded_string(”"); –asigna la ristra nula a sd1

 Asignación

Se pueden asignar entre sí ristras del mismo tipo (son de distinto tipo las ristras limitadas de diferente tamaño).

En las ristras de tamaño fijo, la asignación sólo puede realizarse entre ristras del mismo tamaño:
    sf1 := “1234567890″;

Si se necesita asignar una ristra de un tamaño distinto, se puede emplear la operación “move”:
    move(”prueba”, sf1);

La operación “move” admite hasta 5 parametros:
    procedure Move (Source  : in  String;
                    Target  : out String;
                    Drop    : in  Truncation := Error;
                    Justify : in  Alignment  := Left;
                    Pad     : in  Character  := Space );

  • “Source” es la ristra origen
  • “Target” es la ristra destino
  • “Drop” determina que acción se tomará en caso de que la ristra origen sea mayor que la destino (las acciones posibles son: “left”, que corta la ristra por la izquierda, “rigth”, que corta la ristra por la derecha, y “error”)
  • “Justify” determina, en caso de que la ristra origen sea menor que la destino, cómo se situará (”left”, a la izquierda, “center”, en el centro, y “rigth”, a la derecha)
  • “Pad” determina, en caso de que la ristra origen sea menor que la destino, con qué carácter se rellenarán las posiciones sobrantes

Entrada / Salida

Sólo las ristras de tamaño fijo pueden intervenir en operaciones de entrada/salida (ello no es un gran problema al existir las funciones de conversión).  Una ristra se lee con la operación “get(X)” o “get_line(X,L)”, donde X es una variable de tipo “String” y L es una variable de tipo “Natural” o compatible con él. La primera exige que se entre el número exacto de caracteres, de acuerdo con la declaración de la ristra; la segunda permite la entrada de un número menor de caracteres, e indica en el segundo parámetro el índice del último carácter leído (si el rango de la ristra empieza en uno, esto es igual al número de caracteres leídos).
Cuando se lee una ristra con get_line, los caracteres leídos sustituyen a los correspondientes que hubiese en la ristra, pero el resto quedan como estaban.
    sf1 := “1234567890″;
    get_line(sf1,l_sf1);l_sf1 es una variable de tipo Natural o compatible

Suponiendo que el usuario introduce la ristra “abcde”, sf1 quedará con el valor “abcde67890″.
Asimismo, las ristras de tipo “String” se escriben con las operaciones “put(X)” o “put_line(X)”. La diferencia entre ambas es que la segunda produce un salto de línea después de escribir la ristra.

Cálculo de la longitud

    En ristras de tamaño fijo esta operacion carece de sentido. En ristras de tamaño limitado o de tamaño dinámico se utiliza una función llamada “Length”, que devuelve la longitud de la ristra.
    x := Length(”ULPGC”); –x toma el valor 5

Extracción de una subristra

    En ristras de tamaño fijo esta operación se realiza mediante “slices” (rodajas), igual que en los arrays.
    put_line(sf1(1..5));

En los otros tipos de ristras, se ha de utilizar una función llamada “Slice”.
    function Slice ( Source : in Bounded_String; –Source también puede ser del tipo Unbounded_String
                     Inicio : in Positive;
                     Fin    : in Natural )
    return String;

    Move(Slice(sl1,1,5),sf2); –copia en sf2 la subristra formada por los caracteres de sl1 desde el 1 al 5

Hay que tener cuidado de que los límites del slice estén comprendidos dentro del rango de índices de la ristra, ya que si no se produciría un error.

Concatenación

    Se utiliza como operador de concatenación el operador “&”, que funciona entre todo tipo de ristras y entre ristras y caracteres.
    sl2 := To_Bounded_String(”algo “);
    sl1 := sl2 & “nada”; –sl1 toma el valor “algo nada”

Con las ristras de tamaño limitado se puede utilizar la función “append”:
    function Append (Left, Right : in Bounded_String;
                     Drop        : in Truncation  := Error )
    return Bounded_String;

En este caso, la función devuelve la ristra resultante de concatenar “Left” y “Right”. El parámetro “Drop” indica qué hacer en caso de que la ristra resultante exceda el tamaño máximo permitido. Los valores posibles de Drop son: “Error” (produce un error), “Left” (trunca la ristra por la izquierda) y “Right” (trunca la ristra por la derecha). Existen versiones para concatenar ristras de tamaño limitado con ristras de tamaño fijo o con caracteres.
Con las ristras de tamaño dinámico se puede utilizar el procedimiento “append”:
    procedure Append (Source   : in out Unbounded_String;
                      New_Item : in Unbounded_String);

En este caso, no se necesita el parámetro “Drop”, ya que el tamaño de las ristras no está límitado. Existen versiones de “Append” para concatenar una ristra de tamaño dinámico con una ristra de tamaño fijo o con un carácter, en este orden.

Localización de una subristra

    Se realiza mediante la función “Index”.
    function Index ( Source   : in String;  –Source también puede ser del tipo Bounded_String o Unbounded_String
                     Pattern  : in String )
    return Natural;

 s1,s2 : string(1..5);
   s3  : string(1..10);
   …
   s1 := “12345″;
   s2 := “abcde” ;
   s3 := s1 & s2;
   put(Index(s3,”45″)); –Se escribe un 4

Operadores relacionales

Los operadores relaciones (=, /=, <, >, <=, >=) son aplicables entre ristras atendiendo al alfabeto utilizado.

Más sobre ristras…

Los paquetes “Ada.Strings.Fixed”,  “Ada.Strings.Bounded” y “Ada.Strings.UnBounded” contienen un gran número de otras operaciones para el tratamiento de ristras de tamaño fijo, limitado y dinámico respectivamente.

Excepciones.

Introducción.

En la ejecución de un programa pueden darse muchas situaciones inesperadas, bien errores, bien casos muy particulares no previstos. Tener en cuenta todas las posibles situaciones anómalas oscurecería los algoritmos. El mecanismo de manejo de excepciones permite controlar estas situaciones sin tener que cargar con ese efecto indeseado.
Normalmente, la ocurrencia de un error causa la terminación del programa con un mensaje indicando la excepción ocurrida. En el estándar de Ada están definidas las siguientes excepciones:

Constraint_error.- ocurre cuando se intenta asignar a una variable un valor no válido, o cuando se intenta acceder a una posición de un array fuera del rango permitido.
Program_error.- ocurre en situaciones extrañas cuando parte de un programa no es accesible, o cuando se alcanza el “end” de una función sin encontrar un “return”.
Storage_error.- ocurre cuando se agota la memoria disponible.
Tasking_error.- está relacionado con errores en programas que utilicen programación concurrente.

Las excepciones se lanzan automáticamente cuando se produce un error. También pueden ser lanzadas manualmente mediante una sentencia “raise” en cualquier lugar de un programa.
raise Constrain_error; –produce la excepción Constrain_error

No es normal lanzar manualmente una excepción predefinida; la utilidad de “raise” consiste en lanzar excepciones definidas por el programador.

Declaración de excepciones.

La sintaxis de declaración de excepciones es igual a la de declaración de variables (aunque una excepción NO es una variable).
MiExcepción : exception;

Manejo de excepciones.

Cuando ocurre una excepción se puede:

  1. Capturarla
  2. Ignorarla

Si se captura, entonces cabe:

  1. Controlarla, e intentar que el programa continúe su ejecución.
  2. Reenviarla a otra parte del programa.

La captura de excepciones se realiza en una sección que se inicia con la palabra “exception” y se puede situar al final de cada bloque (begin..end). Dentro de esta sección se utilizan sentencias “when NombreExcepción” para capturar las distintas excepciones y situar el código que realiza el tratamiento de las mismas.
Si se quiere relanzar la excepción, basta con poner la sentencia “raise”. Si se desea, se puede lanzar una nueva excepción con “raise NombreExcepción“.
begin
  …
exception
  when E1 =>
    …
  when E2|E3|…|En =>
    …
  when others =>
    …
end;

Excepciones de entrada/salida.

En el paquete Text_IO se definen las siguientes excepciones relacionadas con las operaciones de entrada/salida.

Status_Error.- ocurre cuando se intenta leer o escribir en un fichero que no está abierto, o abrir un fichero que está abierto.
Mode_Error.- ocurre cuando se intenta leer de un fichero que está abierto para escritura o escribir en un fichero que está abierto para lectura.
Name_Error.- ocurre cuando se intenta abrir un fichero y el nombre externo es incorrecto.
Use_Error.- ocurre cuando se intenta abrir un fichero para un uso ilegal (p.e. si se intenta crear un fichero con un nombre externo que ya existe).
Device_Error.- ocurre cuando se produce un fallo técnico en un dispositivo de entrada/salida.
End_Error.- ocurre cuando se intenta leer de un fichero en el que se ha alcanzado la marca de fin de fichero.
Data_Error.- ocurre cuando se intenta leer un valor entero, real o enumerado y los datos de entrada tienen un formato incorrecto.

Ejemplo.

with Text_IO;
use Text_IO;

procedure Excepciones is
 package Entero_IO is new Integer_IO(Integer);
 package Real_IO is new Float_IO(Float);
 use Entero_IO, Real_IO;

 División_por_cero: exception; –declaración de excepción

 procedure Leer(x,y: out integer) is
 begin
  loop
    begin
      put(”Deme dos números enteros: “);
      get(x); get(y);
      exit;
    exception
      when Data_error => –se controla la excepción y se
                           –recupera la ejecución del programa
         put_line(”Por favor, teclee correctamente”);
         skip_line;
    end;
  end loop;
 end Leer;

 function Divide(x,y: in integer) return float is
   r: float;
 begin
   r := float(x) / float(y);
   return r;
 exception
   when Constraint_error =>   –se relanza una excepción más apropiada
     raise División_por_cero;
 end Divide;

 a, b: integer;
begin
   Leer(a,b);
   put(”El cociente es: “); put(Divide(a,b));new_line;
exception
   when División_por_cero =>  –se deja que el programa acabe de forma controlada
      new_line;
      put_line(”    No se puede dividir por cero”);
end Excepciones;

Ficheros de texto.

Introducción.

Un fichero de texto es básicamente una secuencia de caracteres, algunos de los cuales se pueden interpretar como marcas (fin de línea, fin de página, fin de fichero).

Cláusula de contexto.

Los elementos necesarios para manejar ficheros de texto se encuentran en el paquete “Text_IO”, que habrá que incluir en la cláusula de contexto.
    with Text_IO;
    use Text_IO;

Declaración

Para poder usar ficheros de texto se deben declarar en el programa variables de tipo “File_Type”. Estas variables se conocen como ficheros lógicos, o simplemente ficheros, en oposición a los ficheros externos a los que hacen referencia. El programa manipula los ficheros externos a través de los ficheros lógicos.
    Fichero1 : File_Type;
    Fichero2 : Text_IO.File_Type;

Apertura

Antes de poder hacer transacciones con una variable fichero debe asociarse con un fichero externo; esto se puede hacer con los procedimientos: “Create” y “Open”. El primero sirve para crear un fichero y el segundo para abrir un fichero que ya existe.
    procedure Create (File : in out File_Type;
                      Mode : in File_Mode := Out_File;
                      Name : in String    := “”;
                      Form : in String    := “”);

    procedure Open   (File : in out File_Type;
                      Mode : in File_Mode;
                      Name : in String;
                      Form : in String := “”);

  • File es la variable fichero (fichero lógico).
  • Mode es el modo de apertura (puede tomar los valores: “In_File”, “Out_File”, “Append_File”).
    • In_File abre el fichero en modo lectura.
    • Out_File abre el fichero en modo escritura (si el fichero ya existe, se vacía).
    • Append_File abre el fichero en modo escritura para añadir texto al final de un fichero existente.
  • Name es el nombre del fichero externo (no aparecerá en ningún otro sitio del programa).
  • Form no se usa generalmente; es un parámetro que depende del sistema y puede servir para cosas como proteger el fichero con una password.

    Create(Fichero1,Name => “C:\TEMP\unfichero.txt”);
    Open(Fichero2, In_File, “C:\TEMP\otrofichero.txt”);

En el ejemplo, la primera sentencia crea y abre en modo escritura un fichero externo llamado “C:\TEMP\unfichero.txt”  y lo asocia con la variable “Fichero1″. La segunda sentencia abre en modo lectura un fichero externo llamado “C:\TEMP\otrofichero.txt” y lo asocia con la variable “Fichero2″.
Los errores que pudieran producirse al intentar abrir un fichero pueden controlarse mediante excepciones.
Existe una función para ver si un fichero está abierto:
    function  Is_Open(File : in File_Type) return Boolean;

Por ejemplo: “Is_Open(Fichero1)” devuelve True si el fichero “Fichero1″ está abierto y “False”, si no.
Existen funciones similares para obtener el modo de apertura y nombre de un fichero abierto.
    function  Mode   (File : in File_Type) return File_Mode;
    function  Name   (File : in File_Type) return String;

Cierre

Una vez que se ha terminado de trabajar con un fichero se debe cerrar con la operación “Close”.
    procedure Close  (File : in out File_Type);

    Close(Fichero1); –cierra Fichero1

Transferencia de información

Para realizar lecturas y escrituras desde/a ficheros se utilizan las ya conocidas operaciones “get”, “put”, “get_line”, “put_line”, con un primer parámetro que es la variable fichero de la que se va a leer o escribir.
    get_line(Fichero1,S,L);
    put_line(Fichero2,S(1..L));

El texto leído de Fichero1 en la variable S  se copia en Fichero2. Se supone que S es una ristra de tamaño fijo y L es una variable de tipo Natural.

Estructura del fichero

Existen operaciones para controlar las marcas del fichero en relación a:

  • Líneas: “New_Line”, “Skip_Line” y “End_Of_Line”.
    • New_Line(Fichero1)   –Inicia una nueva línea en Fichero1
    • Skip_Line(Fichero1)  –Salta hasta después de la próxima marca de fin de línea
    • End_Of_Line(Fichero1)–Es una función que devuelve True si se ha alcanzado el fin
                           –de la línea, y False en caso contrario
  • Páginas: “New_Page”, “Skip_Page” y “End_Of_Page”
    • New_Page(Fichero1)   –Inicia una nueva página en Fichero1
    • Skip_Page(Fichero1)  –Salta hasta después de la próxima marca de fin de página
    • End_Of_Page(Fichero1)–Es una función que devuelve True si se ha alcanzado el final
                           –de la página y False en caso contrario
  • Ficheros: “End_Of_File”
    • End_Of_File(Fichero1) es una función que devuelve True si se ha alcanzado el final de fichero1 y False en caso contrario.

Existen funciones para configurar el tamaño de la línea y el tamaño de la página.

Eliminación de un fichero externo

Se realiza mediante la operación “Delete”.
     procedure Delete (File : in out File_Type);

“Delete” borra el fichero externo asociado con la variable fichero que se le pase y cierra ésta.

Además…

En el anexo A.10 del “Ada 95 reference manual” incluido en la ayuda de ObjectAda 7.1 se puede encontrar más información sobre manipulación de ficheros de texto.

Ejemplo

with Text_Io;
use Text_Io;

–El procedimiento Copiar_Fichero copia un fichero de texto, llamado “original.txt”
–en otro llamado “copia.txt”
–precondiciones: debe existir un fichero externo llamado “original.txt”
–                no debe existir un fichero externo llamado “copia.txt”
–postcondiciones: Existe un fichero externo llamado “copia.txt” cuyo contenido es
–                 idéntico al de “original.txt”
procedure Copiar_Fichero is
    F_Entrada,F_Salida: File_Type;
    Línea             : String(1..200);
    Lon_Línea         : Natural;
begin
    –se abren los ficheros
    Open(F_Entrada,Mode => In_File,Name => “original.txt”);
    Create(F_Salida,Name => “copia.txt”);
    –se copia F_Entrada en F_Salida
    while not End_Of_File(F_Entrada) loop
        Get_Line(F_Entrada,Línea,Lon_Línea);
        Put_Line(F_Salida,Línea(1..Lon_Línea));
    end loop;
    –se cierran los ficheros
    Close(F_Entrada);
    Close(F_Salida);
end Copiar_Fichero;

Ficheros uniformes.

Introducción

Un fichero uniforme es una secuencia arbitrariamente larga de elementos del mismo tipo. En Ada se distinguen ficheros secuenciales y ficheros de acceso directo. (Un fichero de texto es básicamente un fichero secuencial de caracteres).

Cláusula de contexto

Los elementos necesarios para manejar ficheros secuenciales se encuentran en el paquete “Sequential_IO”, los ficheros de acceso directo se manejan con el paquete “Direct_IO” que habrá que incluir en la cláusula de contexto.
    with Text_IO,Sequential_IO,Direct_IO;
    use Text_IO;

Declaración

Para poder usar ficheros uniformes se ha de definir previamente el tipo de sus componentes (a menos que sea de un tipo predefinido)
type TPersona is
 record
    Nombre : string(1..20);
    DNI    : string(1..10);
    Edad   : natural;
 end record;

Una vez definido el tipo, se han de definir instancias de los paquetes de manejo de ficheros, según el tipo de acceso que se vaya a utilizar.
    package FichTPersona_Sec is new Sequential_IO(TPersona); — Acceso secuencial
    package FichTPersona_Dir is new Direct_IO(TPersona); — Acceso directo
    use FichTPersona_Sec,FichTPersona_Dir;

Ahora se pueden declarar variables de tipo fichero directo o secuencial:

  Fichero1 : FichTPersona_Sec.File_Type;
  Fichero2 : FichTPersona_Dir.File_Type;

Apertura

Antes de poder hacer transacciones con una variable fichero debe asociarse con un fichero externo; esto se puede hacer con los procedimientos: “Create” y “Open”. El primero sirve para crear un fichero y el segundo para abrir un fichero que ya existe.
    procedure Create (File : in out File_Type;
                      Mode : in File_Mode := Out_File; –InOut_File en acceso directo
                      Name : in String    := “”;
                      Form : in String    := “”);

    procedure Open   (File : in out File_Type;
                      Mode : in File_Mode;
                      Name : in String;
                      Form : in String := “”);

  • File es la variable fichero (fichero lógico).
  • Mode es el modo de apertura (puede tomar los valores: “In_File”, “Out_File”, “Append_File”, “InOut_File”).
    • In_File abre el fichero en modo lectura.
    • Out_File abre el fichero en modo escritura (si el fichero ya existe, se vacía).
    • Append_File abre un fichero secuencial en modo escritura para añadir información al final.
    • InOut_File abre un fichero de acceso directo, permitiendo realizar lecturas y escrituras.
  • Name es el nombre del fichero externo (no aparecerá en ningún otro sitio del programa).
  • Form no se usa generalmente; es un parámetro que depende del sistema y puede servir para cosas como proteger el fichero con una password.

    Create(Fichero1,Name => “C:\TEMP\unfichero.dat”);
    Open(Fichero2,InOut_File,”C:\TEMP\otrofichero.dat”);

En el ejemplo, la primera sentencia crea y abre en modo escritura un fichero secuencial externo llamado “C:\TEMP\unfichero.dat”  y lo asocia con la variable “Fichero1″. La segunda sentencia abre en modo lectura/escritura un fichero externo de acceso directo llamado “C:\TEMP\otrofichero.dat” y lo asocia con la variable “Fichero2″.
Los errores que pudieran producirse al intentar abrir un fichero pueden controlarse mediante excepciones.
Existe una función para ver si un fichero está abierto:
    function  Is_Open(File : in File_Type) return Boolean;

Por ejemplo: “Is_Open(Fichero1)” devuelve True si el fichero “Fichero1″ está abierto y “False”, si no.
Existen funciones similares para obtener el modo de apertura y nombre de un fichero abierto.
    function  Mode   (File : in File_Type) return File_Mode;
    function  Name   (File : in File_Type) return String;

Cierre

Una vez que se ha terminado de trabajar con un fichero se debe cerrar con la operación “Close”.
    procedure Close  (File : in out File_Type);

    Close(Fichero1); –cierra Fichero1

Transferencia de información

Para realizar lecturas y escrituras desde/a ficheros se han de declarar previamente variables del tipo de los componentes del fichero.
    Persona_1,Persona_2,Persona_3 : TPersona;

Las operaciones de entrada/salida se realizan con las operaciones “Read” y “Write”.
    procedure Read(File : in out File_Type; Item : out Element_Type;
                                            From : in  Positive_Count);
    procedure Read(File : in out File_Type; Item : out Element_Type);

Read lee un elemento de un fichero y avanza una posición:

  • File es la variable fichero.
  • Item es la variable, del tipo de los componentes del fichero, en la que se lee el elemento.
  • From es la posición en el fichero desde donde se quiere leer el dato. Sólo es aplicable a ficheros de acceso directo. Si no se especifica se lee de la posición actual en el fichero.

    procedure Write(File : in out File_Type; Item : in Element_Type;
                                             To   : in Positive_Count);
    procedure Write(File : in out File_Type; Item : in Element_Type);

Write escribe un elemento en un fichero y avanza una posición:

  • File es la variable fichero.
  • Item es la variable, del tipo de los componentes del fichero, cuyo contenido se escribe en el fichero
  • To es la posición en el fichero donde se quiere escribir el dato. Sólo es aplicable a ficheros de acceso directo. Si no se especifica se escribe en la posición actual en el fichero.

Estructura del fichero

Existe una operación para controlar la marca de fin de fichero:

  • End_Of_File(Fichero1) es una función que devuelve True si se ha alcanzado el final de Fichero1 y False en caso contrario.

Operaciones con ficheros de acceso directo

El tamaño, en número de componentes, de un fichero de acceso directo se obtiene mediante la función “Size”, que devuelve un número comprendido entre 0 y el máximo tamaño posible definido en la implementación del compilador de Ada que se esté usando.
    function Size(File : in File_Type) return Count;

Se puede saltar a una posición específica de un fichero utilizando el procedimiento “Set_Index”
    procedure Set_Index(File : in out File_Type; To : in Positive_Count);

La posición actual en el fichero se puede averiguar mediante la función “Index”, que devuelve un valor comprendido entre 1 y el máximo tamaño posible definido en la implementación.
    function Index(File : in File_Type) return Positive_Count;

Eliminación de un fichero externo

Se realiza mediante la operación “Delete”.
     procedure Delete (File : in out File_Type);

“Delete” borra el fichero externo asociado con la variable fichero que se le pase y cierra ésta.

Además…

En los anexos A.7, A.8 y A.9 del “Ada 95 reference manual” incluido en la ayuda de ObjectAda 7.1 se puede encontrar más información sobre manipulación de ficheros.

Punteros.

Introducción

Los tipos puntero representan direcciones de memoria y son un importante mecanismo para llevar a cabo una gestión dinámica de la memoria, así como para crear estructuras de datos dinámicas complejas.

Declaración

En Ada los punteros se denominan “accesos”. Para declarar punteros que puedan referenciar a variables dinámicas se utiliza la palabra reservada access.
    type TPersona is record
        Nombre : string(1..20);
        Apellido1 : string(1..20);
        Apellido2 : string(1..20);
    end record;
    type TP_Persona is access TPersona; –Tipo de los punteros a TPersona dinámicos
    type TP_Integer is access integer; –Tipo de los punteros a integer dinámicos

    Pint1, Pint2 : TP_Integer; –Punteros a integer dinámico
    PPer1, PPer2 : TP_Persona; –Punteros a TPersona dinámicos
    i : integer; –Variable estática de tipo integer
    p : TPersona; –Variable estática de tipo TPersona

Los punteros se inicializan automáticamente con el valor null. indicando que no referencian a ninguna variable.

Ubicación dinámica

Para crear una variable dinámica se utiliza la palabra reservada new seguida del tipo de la variable que se quiere crear:
    Pint1 := new integer; –Se crea una variable dinámica de tipo integer
    PPer1 := new TPersona; –Se crea una variable dinámica de tipo TPersona

Las variables creadas dinámicamente se pueden inicializar en su creación:
    Pint2 := new integer’(35);
    PPer2 := new TPersona’(”Pepe      “,”Pérez     “,”Pérez     “);

Además, una variable puntero puede ser inicializada en su declaración:
    Pint : TP_Integer := new integer’(35);

Acceso a las variables referenciadas por los punteros

Para acceder o modificar el contenido de una variable referenciada por un puntero, se utiliza el cualificador all:
    i := Pint2.all; –Se asigna a i el valor de la variable referenciada por Pint2
    PPer1.all := PPer2.all;
      –Se asigna a la variable referenciada por PPer1 el valor
      –de la variable referenciada por PPer2
    Pint1 := Pint2; –Pint1 y Pint2 referencian a la misma variable
    i := Pint1; –ERROR, i es de tipo integer y Pint es de tipo puntero

Cuando la variable referenciada es un record se puede acceder a cada campo utilizando simplemente el cualificador punto (.):
    p.Nombre := PPer1.Nombre;
    p.Apellido1 := PPer1.all.Apellido1; –El .all es inncesario

Si la variable referenciada es un array se puede acceder a sus elementos individuales utilizando paréntesis del modo habitual:
    type TArray is array (integer range <>)  of integer;
    type TP_Array is access TArray;
    v : TP_Array := new TArray’(1,2,3,4,5);
    …
    i := v(3);

Liberación de variables dinámicas

En Ada, una variable dinámica se libera automáticamente cuando finaliza la ejecución de la parte del programa donde está definido el tipo puntero correspondiente. Por tanto, para realizar un control más eficiente de la memoria es necesario emplear operaciones de liberación explícita de las variables creadas dinámicamente cuando ya no sean necesarias. Para ello se necesita, primero, incluir “Unchecked_Deallocation” en la cláusula de contexto:
    with Text_io, Unchecked_Deallocation;

A continuación, se debe instanciar el procedimiento genérico “Unchecked_Deallocation” para el tipo de dato y tipo de puntero que se quiere liberar:
    procedure Libera_Persona is new Unchecked_Deallocation(TPersona,TP_Persona);

Ahora, se puede usar Libera_Persona para liberar variables dinámicas de tipo TPersona referenciadas por variables puntero de tipo TP_Persona:
    Libera_Persona(PPer1);

Punteros que apuntan a variables estáticas

Las variables puntero almacenan direcciones. Todas las variables tienen una dirección; sería lógico querer que en una variable puntero a un tipo T se pueda almacenar la dirección no sólo de variables de tipo T creadas dinámicamente, sino también de variables estáticas de tipo T. En Ada los punteros sólo pueden almacenar direcciones de variables dinámicas, a menos que se declaren “access all”:
    type PGen_TPersona is access all TPersona;
        –Las variables de tipo PGen_TPersona pueden referenciar
        –variables de tipo TPersona, tanto estáticas como dinámicas
    PGPer : PGen_TPersona;

Para que la dirección de una variable estática pueda ser asignada a un puntero, ésta debe  permitirlo en su declaración, utilizando la palabra aliased:
    p1: aliased TPersona;

La dirección de una variable declarada así puede obtenerse mediante el atributo access:
    PGPer := p1′access; –Asigna a PGPer la dirección de p1

Se puede imponer restricción de lectura a un puntero, de forma que no se pueda modificar el objeto al que apunta, pudiendo entonces utilizarse para referenciar una constante, o para referenciar una variable de forma segura, con la garantía de que no podrá modificarse a través del puntero:
    type Punt_Const_Int is access constant integer; –Tipo de los punteros a enteros constantes
    …
    type Punt_Int is access all integer; –Tipo de los punteros a variables integer (estáticos o dinámicos)
    PCI1,PCI2 : Punt_Const_Int;
    Pint3 : Punt_Int;
    i : aliased integer;
    …
    PCI1 := i’access;
    Pint3 := i’access; –PCI1 y Pint3 son alias de i
    i := 3;
    Pint3.all := 4; –Modifica i
    PCI1.all := 5; –ERROR, no se puede modificar i a través de PCI
    PCI2 := new integer’(9); –Se puede hacer new siempre que se inicialice

Declaraciones incompletas de tipos

Una aplicación frecuente de los punteros es la creación de estructuras de datos dinámicas, por ejemplo listas encadenadas. Este tipo de estructuras están formadas por un conjunto de nodos conectados mediante campos de enlace que son punteros a nodos del mismo tipo. Para estos nodos sería necesaria una definición de tipo tal como:
    type NodoLista is record
        Info: integer;
        Siguiente: PNodoLista;
    end record;

donde PNodoLista es el tipo de los punteros a objetos de tipo NodoLista. Esto significa que debería existir una definición para PNodoLista:
    type PNodoLista is access NodoLista;

Esta definición debería estar antes que la de NodoLista, para que sea conocida cuando se declara el campo siguiente de éste; pero entonces NodoLista no se conocería cuando se intenta definir PNodoLista, creándose un círculo vicioso. La solución consiste en empezar con una declaración incompleta de NodoLista que establezca que tal tipo va a existir, pero sin concretarlo, cosa que debe hacerse más adelante en la misma parte del programa. Una declaración incompleta comienza igual que una definición completa, pero termina con un punto y coma (;) donde ésta tiene la palabra is:
    type NodoLista;
    type PNodoLista is access NodoLista;
    type NodoLista is record
        Info: integer;
        Siguiente: PNodoLista;
    end record;

Estructura general de un programa en Ada.

Formalmente un programa en Ada se estructura en unidades distribuidas en uno o varios ficheros. Las unidades pueden ser:

  • subprogramas.- definen algoritmos y se clasifican en procedimientos y funciones.
  • paquetes (package).- definen módulos que son colecciones de entidades (tipos, constantes, subprogramas, …).
  • tareas (task) .- definen acciones que pueden ejecutarse en paralelo con otras.
  • unidades protegidas (protected unit).- sirven para coordinar la compartición de datos entre tareas concurrentes.
  • unidades genéricas.- sirven para definir componentes reusables.

Paquetes.

Los paquetes son la clase de unidad más importante. En general un programa en Ada se compone de un conjunto de paquetes y un procedimiento principal. La claúsula de contexto “with” sirve para declarar que se van a usar los servicios de un paquete.

Un paquete se divide generalmente en dos partes: especificación  e implementación (body). Ambas partes se sitúan casi siempre en ficheros diferentes, con las extensiones “*.ads” y “*.adb”, respectivamente.

La estructura de la especificación de un package es la siguiente:

    package nombre_de_la_unidad is
        –declaraciones
    private
        –declaraciones privadas
    end nombre_de_la_unidad;

Las declaraciones que aparecen antes de la palabra “private” constituyen la parte visible, o interfaz, del paquete. Las que aparecen después son privadas; no obstante, la parte privada es opcional, y si no aparece la palabra “private” se entiende que el paquete no tiene parte privada.

Las declaraciones pueden ser desde declaraciones de tipos hasta prototipos de procedimientos o funciones. Un prototipo de un procedimiento o función es su cabecera, pero cambiando la palabra “is” por un punto y coma (”;”), lo que indica que el subprograma se desarrollará más tarde (en la implementación del paquete).

La declaración de un paquete requiere una implementación si hay declaraciones que requieran completarse (por ejemplo, el prototipo de una función requiere su desarrollo). La estructura de un “package body” es:

    package body nombre_de_la_unidad is
        –desarrollo del package
    end nombre_de_la_unidad;

Todas las declaraciones hechas en la especificación del paquete se pueden usar en la implementación (body), aunque estén en la parte “private”. Las declaraciones hechas en el “package body” sólo se pueden usar en el mismo.

Tipos “private” y “limited private”.

Si se declara un tipo en la especificación de un paquete, cualquiera que utilice el paquete podrá acceder a los elementos internos de la implementación del mismo. Esto hace que la aplicación se vuelva dependiente de la forma en que se ha implementado el nuevo tipo. Para evitar esta dependencia –y poder cambiar la implementación si es necesario– se declara el tipo como “private” en la parte pública del paquete y se pone su definición en la parte privada. Al declarar un tipo como “private” su definición queda oculta, y el usuario del paquete sólo podrá utilizar con él las operaciones que se hallan declarado en la parte pública del paquete, además de la asignación (:=), la comparación de igualdad (=) y la de desigualdad (/=).

    package Manejo_De_Claves is
        type Clave is private;
        Clavenula : constant Clave;
        procedure Tomar_Clave(C : out Clave);
        function “<”(X, Y : in Clave) return Boolean;
    private
        Max : constant := 2 ** 16 - 1;
        type Clave is range 0 .. Max;
        Clavenula : constant Clave := 0;
    end Manejo_De_Claves;

Huelga decir que el paquete Manejo_De_Claves necesita un “package body” para completar su definición.

    package body Manejo_De_Claves is
      procedure Tomar_Clave(C : out Clave) is
      begin
          — Cuerpo del procedimiento
      end Tomar_Clave;

      function “<”(X, Y : in Clave) return Boolean is
      begin
          — Cuerpo del operador
      end “<”;
end Manejo_De_Claves;
Si además se quiere deshabilitar las operaciones de asignación y comparación de igualdad/desigualdad, habrá que declarar el tipo como “limited private“.

Unidades genéricas en Ada.

Ada permite crear unidades genéricas, es decir, con parámetros que se pueden concretar para diferentes instancias de la unidad. Para ello basta anteceder la unidad correspondiente con la palabra “generic”; los parámetros formales de la unidad se sitúan en la zona comprendida entre la palabra “generic” y el comienzo de la unidad propiamente dicha. Vemos un ejemplo para el caso de un procedimiento genérico:
    …
    generic
        type TElemento is private;
    procedure Algo(Elemento:in TElemento) is
        –Declaraciones
    begin
        –Acciones
    end Algo;

Para utilizar el procedimiento “Algo” primero se deberán crear instancias para los tipos apropiados:
    …
    procedure Algo_entero is new Algo(TElemento=>integer);
    procedure Algo_carácter is new Algo(character);

Ahora, ya se pueden invocar los procedimientos “Algo_entero” y “Algo_carácter”, según convenga:
    Algo_entero(30);
    Algo_carácter(’a');

Paquetes genéricos

Los paquetes genéricos en Ada son los que soportan la genericidad de los tipos abstractos de datos (tad) en este lenguaje. El formato general de un paquete genérico es, en la especificación del paquete:
    generic
        –Zona de declaración de parámetros formales
    package nombre_del_paquete_genérico is
        –Zona de uso de los parámetros formales
    end nombre_del_paquete_genérico;

En la implementación del paquete:
    package body nombre_del_paquete_genérico is
        –Zona de uso de los parámetros formales
    end nombre_del_paquete_genérico;

Los parámetros formales se pueden emplear en la implementación del paquete (package body) sin más requerimientos.

La forma de usar un paquete genérico es crear una instancia del paquete genérico en la que se especifiquen todos los parámetros formales requeridos. Esta sentencia tiene la forma:
    package nombre_instancia is new nombre_del_paquete_genérico(parámetros_actuales,…);

Parámetros formales

Los parámetros formales pueden ser: objetos, tipos o subprogramas. Al igual que los parámetros de los subprogramas, los parámetros de un paquete genérico pueden tener un valor por defecto. Los parámetros formales se declaran de distinta forma según su naturaleza:

  • Tipos privados: “private”, “limited private”, “tagged private” y “tagged limited private”. La definición del tipo privado corresponde, en este caso, al llamador –no va a aparecer en la parte private, si estamos desarrollando un paquete–, (type Tipo is private;).
  • Tipos escalares:
    • Discretos. Se utiliza el símbolo “<>”, (type TDiscreto is <>;).
    • Enteros con signo. Se utiliza “range <>”, (type TEntero is range <>;).
    • Modulares. Se utiliza “mod <>”, (type TModular is mod <>;).
    • Reales en coma flotante. Se utiliza “digits <>”, (type TRealFlotante is digits <>;).
    • Reales en coma fija. Se utiliza “delta <>”, (type TRealFijo is delta <>;).
    • Decimales. Se utiliza “delta <> digits <>”, (type TDecimal