|
|
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.
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:
- Cabecera
- Declaraciones locales
- 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 );
|
|
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:
- Capturarla
- Ignorarla
Si se captura, entonces cabe:
- Controlarla, e intentar que el programa continúe su ejecución.
- 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 is delta <> digits <>;).
- Arrays. Hay que incluir como parámetros, el tipo de los elementos del array, el tipo del índice del array y el tipo array:
generic
type TElemento is private;
type Índice is (<>);
type Vector is array (Índice range <>) of TElemento;
package P is
…
end P; - Punteros. Hay que especificar el tipo puntero y el tipo apuntado:
generic
type TNodo is private;
type TP_Nodo is access TNodo;
package P is
…
end P; - Subprogramas. Se utiliza la palabra “with” precediendo al protocolo del subprograma que se espera:
generic
type TElemento is private;
with procedure Acción(X : in TElemento);
procedure Iterar(Seq : in Secuencia_de_TElemento);
…
procedure Asignar_Elemento(X : in Item);
…
– Es posible la siguiente instancia
procedure Asignar_Lista is
new Iterar(TElemento => Item,
Acción => Asignar_Elemento);
Se puede especificar un nombre por defecto para el subprograma pasado por parámetro, utilizando “is nombre”:
with procedure Acción (X : in TElemento) is Escribir;
Si se quiere que el nombre por defecto sea el mismo que el del parámetro formal, se pondrá como nombre “<>”:
with procedure Acción (X : in TElemento) is <>;
En caso de especificar un nombre por defecto se podrá omitir el parámetro al instanciar la unidad.
- Paquetes. Se utiliza la palabra “with” precediendo a la instanciación del paquete que queremos como parámetro formal.
with package Nombre_formal is new Nombre_genérico Parte_formal_real;
Ejemplo:
El siguiente código muestra la declaración, implementación y uso de un paquete genérico para manejar listas ordenadas.
Fichero de especificación (*.ads)
– Se declara un paquete genérico para manejar listas ordenadas
– Se pasan como parámetros:
– El Tamaño máximo de la Lista (por defecto 10)
– El tipo de dato que se va a almacenar en la Lista
– Una operación de comparación
– Una acción aplicable a un elemento de la Lista
generic
Tamaño : Natural := 10; – Tamaño por defecto
type Telemento is private; – Tipo de dato a almacenar.
— El paquete no necesita conocer de Telemento mas que
— las operaciones que vienen a continuación
with function “<”(X,Y: in Telemento) return Boolean is <>;
– Operación de comparación de elementos
with procedure Accion(X: in Telemento);
– Operación que se aplica a un elemento
package Lista_Ordenada is
type Tlista is private; — La implementación de la lista queda oculta
procedure Insertar(X: in Telemento; L: in out Tlista);
– Inserta un elemento en la lista
procedure Extraer(I: in Natural; L: in out Tlista);
– Extrae un elemento de la lista
procedure Iterar(L: in Tlista);
– Recorre la lista aplicando una operación a cada elemento
private – Estructura de datos de la lista
type Contenedor is array (1..Tamaño) of Telemento;
type Tlista is record
Elementos : Contenedor;
Num_Elementos : Natural := 0;
end record;
end Lista_Ordenada;
Fichero de implementación (*.adb)
– Implementación de las operaciones de la lista ordenada
package body Lista_Ordenada is
procedure Insertar(X: in Telemento; L: in out Tlista) is
I : Natural;
begin
I := 1;
– Esquema de búsqueda para la localización de la posición
while I <= L.Num_Elementos and then L.Elementos(I) < X loop
I := I + 1;
end loop;
L.Num_Elementos := L.Num_Elementos + 1;–Posible Constrain Error
if I < L.Num_Elementos then
– Se desplazan los elementos para insertar el nuevo
for J in reverse I+1..L.Num_Elementos loop
L.Elementos(J) := L.Elementos(J-1);
end loop;
end if;
L.Elementos(I) := X;
end Insertar;
procedure Extraer(I: in Natural; L: in out Tlista) is
begin
– Se desplazan los elementos para cubrir el hueco de la extracción
for J in I..L.Num_Elementos-1 loop
L.Elementos(J) := L.Elementos(J+1);
end loop;
L.Num_Elementos := L.Num_Elementos - 1;–Posible Constrain Error
end Extraer;
procedure Iterar(L: in Tlista) is
begin
– Esquema de recorrido sobre la lista
for J in 1..L.Num_Elementos loop
Accion(L.Elementos(J));– Se aplica la acción pasada como parámetro
end loop;
end Iterar;
end Lista_Ordenada;
Fichero del programa principal (*.ada)
with Text_Io,Lista_Ordenada;
use Text_Io;
procedure Prueba_De_Contenedor is
package Enteros_Io is new Integer_Io(Integer);
use Enteros_Io;
procedure Escribir_Elemento(X: in Integer) is
begin
Put(X);
New_Line;
end;
— Se crea una instancia del paquete genérico Lista_Ordenada, llamada
— Lista_Enteros, con un tamaño máximo de 20, los elementos que se almacenan
— son de tipo integer, la operación de comparación es el operador “<” de
— los integer (al no especificarlo se usa por defecto), como accion se pasa
— el procedimiento Escribir_Elemento
package Lista_Enteros is new Lista_Ordenada(20,Integer,Accion => Escribir_Elemento);
use Lista_Enteros ;
L: Tlista;
begin
for J in reverse 1..15 loop
Insertar(J,L);
end loop;
Iterar(L);
end Prueba_De_Contenedor;
Apéndices
Entorno de Programación en Ada
Sistema de desarrollo:
Como entorno de desarrollo de programación en Ada 95 se empleará “ObjectAda for Windows“. Este entorno de desarrollo integra los siguientes componentes:
i) Compilador de Ada 95.
ii) Gestor de proyectos.
iii) Editor sensible a Ada 95.
iv) Depurador.
Al ejecutar el entorno “OA.exe” nos encontramos con: un menú principal, cuatro de las cinco barras de herramientas y la ventana dividida en dos secciones, el área de trabajo principal y la ventana de mensajes. La mayoría de las opciones seleccionables por menú nos las encontramos también en una de las cinco barras de herramientas: Estándar, Proyecto, Edición, Depurado, Examinar. Por defecto la barra de examinar no está visible. El área de trabajo es la zona de edición de ficheros, proyectos y objetivos, la ventana de mensajes está compuesta de tres secciones donde se muestra la salida de la ejecución de distintas herramientas del entorno.

El entorno maneja ficheros, proyectos y objetivos. Un proyecto representa un conjunto de ficheros relacionados. Un objetivo (target) representa un subconjunto de ficheros usados para construir uno o más programas y las opciones de compilación y construcción usadas en el proceso. Los “targets” se pueden usar para controlar la forma en que se construyen los programas. Distintos “targets” pueden tener los mismos ficheros pero distintas opciones, o las mismas opciones y distintos ficheros.
Por defecto, un proyecto se crea con dos objetivos: “release” y “debug”. El primero está pensado para obtener programas definitivos, el segundo está pensado para ejecución del programa en una fase de pruebas.
Creación de un proyecto.

Para crear un nuevo proyecto es suficiente con escoger la opción “New” del submenú “File” y seleccionar “Project” en el dialogo que se muestra. El proyecto se creará en una carpeta, preferiblemente vacía, donde el entorno creará subcarpetas de trabajo. En el diálogo de creación de proyectos (New project) hay que especificar tanto el nombre del proyecto como la carpeta (Directory) donde queremos crearlo.

Una vez creado el proyecto se puede establecer con “Project|Files” qué ficheros lo forman. Estos ficheros deben existir en el momento de añadirlos al proyecto (cuando se crea un proyecto se despliega automáticamente el diálogo de “Project|Files”, pero como los ficheros del proyecto normalmente no existirán en ese momento, es mejor cerrarlo y volver a activarlo vía ménu cuando los mismos se hayan creado).
Para crear ficheros se escoge la opción “File|New”, igual que para crear el proyecto, pero ahora se deberá elegir “Code/Text” en el diálogo de creación.
Los ficheros donde se almacena el proyecto tienen la extensión “.prj” . Los ficheros fuente tienen extensiones “.ada”, “.adb” y “ads”. La extensión “.ada” corresponde al fichero que contiene el procedimiento principal del proyecto, que servirá como punto de inicio del programa.
Para ejecutar un programa es necesario compilar cada unidad y enlazarlas sin ningún error. Se utilizará la opción “Project|Compile” para compilar y “Project|Build” para enlazar. Con “Project|Execute” se ejecuta el programa. No debe olvidarse compilar y enlazar siempre que se hagan cambios.
Para trasladar, guardar o copiar un proyecto es suficiente con copiar los ficheros “.prj”, “.ada”, “.adb” y “ads”.
Operadores principales
| Clasificación | Operador | Descripción | Asociación |
| Delimitadores | () | Llamada a función | De izquierda a derecha |
| () | Elemento de un array | ||
| . | Operador punto para los miembros de un estructura | ||
| Aritméticos | ** | Exponenciación | De derecha a izquierda |
| abs | Valor absoluto | ||
| NOT | no lógico | ||
| * | Multiplicación | De izquierda a derecha | |
| / | División | ||
| rem | Resto | ||
| - | Menos unario | ||
| = | Suma unaria | ||
| & | Concatenación de string | De izquierda a derecha | |
| + | Suma | ||
| - | Sustracción | ||
| Relacionales | = | Igualdad lógica | De izquierda a derecha |
| /= | Desigualdad lógica | ||
| < | Menor que | ||
| <= | Menor o igual que | ||
| > | Mayor que | ||
| >= | Mayor o igual que | ||
| in | Miembro de | ||
| not in | No miembro de | ||
| Lógicos | and | Y lógico | De izquierda a derecha |
| or | O lógico | ||
| xor | O exclusive | ||
| and then | |||
| or else | |||
| Asignación | := | Asignación simple | De derecha a izquierda |
| Separador | , | Separador coma | De izquierda a derecha |
Palabras reservadas.
| abort
and case digits exception goto loop of pragma record select then with |
abs
array constant do exit if mod or private rem separate type xor |
accept
at declare else for in new others procedure renames subtype use |
access
begin delay end function is not out raise return task when |
all
body delta entry generic limited nul package reange reverse terminate while |
Funciones matemáticas elementales.
Existe un paquete generico llamado Ada.Numerics.Generic_Elementary_Functions que ofrece las siguientes funciones matemáticas:
function Sqrt (X : Float_Type’Base) return Float_Type’Base;
function Log (X : Float_Type’Base) return Float_Type’Base;
function Log (X, Base : Float_Type’Base) return Float_Type’Base;
function Exp (X : Float_Type’Base) return Float_Type’Base;
function “**” (Left, Right : Float_Type’Base) return Float_Type’Base;
function Sin (X : Float_Type’Base) return Float_Type’Base;
function Sin (X, Cycle : Float_Type’Base) return Float_Type’Base;
function Cos (X : Float_Type’Base) return Float_Type’Base;
function Cos (X, Cycle : Float_Type’Base) return Float_Type’Base;
function Tan (X : Float_Type’Base) return Float_Type’Base;
function Tan (X, Cycle : Float_Type’Base) return Float_Type’Base;
function Cot (X : Float_Type’Base) return Float_Type’Base;
function Cot (X, Cycle : Float_Type’Base) return Float_Type’Base;
function Arcsin (X : Float_Type’Base) return Float_Type’Base;
function Arcsin (X, Cycle : Float_Type’Base) return Float_Type’Base;
function Arccos (X : Float_Type’Base) return Float_Type’Base;
function Arccos (X, Cycle : Float_Type’Base) return Float_Type’Base;
function Arctan (Y : Float_Type’Base;
X : Float_Type’Base := 1.0) return Float_Type’Base;
function Arctan (Y : Float_Type’Base;
X : Float_Type’Base := 1.0;
Cycle : Float_Type’Base) return Float_Type’Base;
function Arccot (X : Float_Type’Base;
Y : Float_Type’Base := 1.0) return Float_Type’Base;
function Arccot (X : Float_Type’Base;
Y : Float_Type’Base := 1.0;
Cycle : Float_Type’Base) return Float_Type’Base;
function Sinh (X : Float_Type’Base) return Float_Type’Base;
function Cosh (X : Float_Type’Base) return Float_Type’Base;
function Tanh (X : Float_Type’Base) return Float_Type’Base;
functio