Este es el resumen que hice para estudiar la asignatura de Programación I, en la carrera de Ingeniería Técnica de Informática de Gestión en la UNED.
Esta asignatura utiliza el lenguaje de programación MODULA-2. Es un lenguaje demasiado obsoleto, pero muy bueno para aprender a programar en cualquier lenguaje, por lo estricto que es.
Repito, es un resumen. Mientras lo transcribía he visto que está muy resumido, no se puede utilizar a modo de documento de consulta, si no mas bien como recordatorio.
Maquinas y programas
Maquinas programables
El concepto de maquina lleva asociado el de un proceso de funcionamiento en el que las diferentes operaciones se realizan de forma sucesiva o simultánea
Desde el punto de vista del funcionamiento, las clasificamos:
Concepto de computo:
Un computo es una operación de tratamiento de información. En informática identificamos el concepto de computo con el de tratamiento de la información
Concepto de computador:
Un computador se define como una maquina programable para tratamiento de la información
Un programa de computador es una descripción de un computo. Ademas es también una descripción del comportamiento de una maquina y podemos considerarlo como una maquina virtual
Un computador posee elementos fijos (maquina base -> Hardware) y modificables (programas -> software) La estructura general de un computador se puede representar
| Memoria | ||
| Procesador | ||
| Datos de entrada | Entrada / Salida | Resultados de salida |
Programación e ingeniería de software:
Para realizar un determinado tratamiento de información con ayuda de un computador habrá sido necesario:
Programación
El desarrollo de programas complejos exige un equipo mas o menos numeroso de personas que trabajan de manera organizada. Las técnicas para desarrollo constituye la ingeniería de software
Programación e ingeniería del software no son disciplinas independientes si no complementarias
El desarrollo de software en gran escala consiste en descomponer el trabajo total en programación en partes independientes para ser desarrollada por miembros individuales.
Objetivos de la programación
Las técnicas de programación han de establecerse con el objetivo de ser una base adecuada para la ingeniería del software
Los objetivos particulares de la programación:
Si tenemos que ordenar los distintos objetivos, primero la corrección, segundo la claridad y tercero la eficiencia
Lenguajes de programación
Cada modelo de computador podrá utilizar una forma particular de codificación de programas que no tiene por que coincidir con la de otros modelos
Código de maquina ó Lenguaje de maquina: la forma de codificar de una maquina en particular
Un programa codificado en el lenguajes de un modelo de maquina, no podrá ser ejecutado en otro distinto. Para que funcione en diferentes maquinas tenemos que preparar versiones particulares para cada maquina, lo que multiplica el costo del desarrollo
El código maquina es muy difícil de leer por personas, esta compuesto por código numérico sin sentido
Los lenguajes de programación sirven para representar programas en forma texto para que pueda ser leído por una persona con facilidad, ademas, son formas de representación independiente de la maquina que lo vaya a usar
Compiladores e interpretes
Procesadores de lenguajes: Realizan el tratamiento de la información en forma de texto que representa el programa en el lenguaje de programación simbólico
Un compilador es un programa que traduce programas de un lenguaje de programación simbólico a código maquina
Programa fuente o lenguaje fuente; A la representación del programa en lenguaje simbólico
Programa objeto o lenguaje objeto: A la representación del programa en código maquina
Interprete es un programa que analiza la descripción simbólica del programa fuente y realiza las operaciones oportunas
El proceso mediante interprete es mas sencillo, que mediante compilador, ya que no hay que realizar dos fases. Su principal inconveniente es que su ejecución es mas lenta.
Modelos abstractos de computo
El modelo abstracto recoge los elementos básicos y formas de combinación en forma abstracta, prescindiendo de la notación concreta usada en cada lenguaje de programación para representarlo.
Hay diversos modelos:
Modelo funcional: Se basa en el empleo de funciones
Modelo de flujo de datos: Cada operador esta conectado mediante un cuadrado con entradas y salidas
Modelo de programación lógica: Un programa consiste en plantear de manera formal un problema a base de declarar una serie de elementos conocidos.
Modelo imperativo: Responde a la arquitectura Von Neumann. Aparece como una lista de ordenes o instrucciones.
Notación BNF
Elemento_no_terminal: Este estilo se emplea para escribir el nombre de un elemento gramatical que habrá de ser definido por algún regla. Cualquier elemento a la izquierda del metasímbolo ::= será no terminal y aparecerá con este estilo.
Elemento_terminal: Este estilo se emplea para representar los elementos que forman parte del lenguaje Modula-2, es decir, que constituyen el texto de un programa. Si aparecen en una regla deberán escribirse exactamente como se indica.
Valores y tipos:
Tipos predefinidos:
Operaciones de escritura simple:
En todas las versiones de Modula-2 deben estar disponibles ciertos módulos estándar, con la definición de operaciones de escritura normalizadas. En este apartado describiremos operaciones disponibles en los módulos llamados InOut y RealInOut.
Estructura de un programa completo:
MODULE EscribirHola;
(* Este programa escribe Hola*)
FROM InOut IMPORT WriteString, WriteLn;
BEGIN
WriteLn;
END EscribirHola.
El texto del programa comienza con una cabecera en la que después de la palabra MODULE, se pone el nombre que le damos al módulo de programa.
A continuación se declara la lista de elementos importados de otros módulos. Esta lista de importados puede estar vacía si no se necesita ninguna colaboración de otros módulos.
En la lista de importados se indica desde que modulo se importan los elementos y la lista de elementos que se quieren importar, usando las palabras inglesas, FROM e IMPORT.
La parte final del programa contiene las sentencias ejecutables correspondiente a las acciones a realizar, escritas entre las palabras inglesas BEGIN y END.
Las diferentes partes del programa se separan una de otra mediante (
. Al final del texto del programa se repite el nombre del módulo como comprobación y se acaba con un (.)
Descripción formal de la estructura de un programa:
La estructura de un bloque solamente puede indicarse de momento en forma simplificada, ya que solo se han visto los elementos mínimos necesarios para escribir un programa.
Descripción formal de la estructura de un programa
Identificadores
AND, ARRAY, BEGIN, BY, CASE, CONST, DEFINITION, DIV, DO, ELSE, ELSIF, END, EXIT, EXPORT, FOR, FROM, IF, IMPLEMENTATION, IMPORT, IN, LOOP, MOD, MODULE, NOT, OF, OR, POINTER, PROCEDURE, QUALIFIES, RECORD, REPEAT, RETURN, SET, THEN, TO, TYPE, UNTIL, VAR, WHILE, WITH.
Identificadores predefinidos
Constantes:
Declaración_de_constantes ::= CONST Asociación_constante;
Asociación_constante ::= Identificador = Expresión_constante;
Variables:
Declaracion de variables:
Declaración_de_variables ::= VAR {Lista_de_variables;
Lista_de_variables ::= Lista_de_identificadores : Tipo
Lista_de_identificadores ::= Identificador {, Identificador}
Tipo ::= INTEGER | CARDINAL | REAL | CHAR
Sentencia de asignación
Sentencia_de_asignación ::= Variable := Expresión
Variable ::= Identificador
Operaciones de lectura simple
Los datos a leer se suministran externamente en forma de texto, es decir, como una serie de caracteres seguidos que pueden incluir saltos de línea de vez en cuando. El salto de línea corresponde a la tecla marcada intro.
ReadInt;
ReadCard;
ReadReal;
Read;
Desarrollar programas por refinamientos sucesivos
Esta técnica es parte de las recomendaciones de una metodología general de desarrollo de programas denominada programación estructurada.
Esta técnica consiste en expresar inicialmente el programa a desarrollar como una acción global, que si es necesario se ira descomponiendo en acciones mas sencillas hasta llegar a acciones simples que pueden ser expresadas directamente como sentencias del lenguaje de programación.
Desarrollo de un esquema secuencial:
Metodología de desarrollo de un esquema de selección
Un esquema de selección consiste en plantear una acción compuesta como la realización de una acción entre varias posibles, dependiendo de ciertas condiciones, es decir, se trata de elegir para realizar una sola entre varias posibles alternativas.
Hay que hacer:
Metodología de desarrollo de un esquema de iteración
Una iteración o bucle consiste en la repetición de una acción o grupo de acciones hasta conseguir el resultado deseado. Para desarrollar un esquema de iteración dentro de un programa deberemos identificar cada uno de sus elementos componentes. Al hacerlo hay que identificar simultáneamente las variables adecuadas para almacenar la información necesaria.
Aspectos de estilo:
El estilo de redacción del programa en su forma final es algo fundamental para conseguir que sea claro y fácilmente comprensible por parte de quienes hayan de leerlo.
Encolumnado:
Comentarios:
(***********************
* Aquí metes lo que te de la gana
* como cabecera del programa
************************)
(*===========================
Comentario de Sección
============================)
(*-- Aquí cuentas lo que esta haciendo la orden siguiente --*)
(* A la izquierda tienes el código que estas utilizando y esto a la dcha.*)
Programación estructurada:
La programación estructura es una metodología de programación que trata fundamentalmente de construir programas que sean fácilmente comprensibles.
Esta metodología esta basada en la técnica de desarrollo de programas por refinamientos sucesivos.
La estructura de los programas imperativas se representan tradicionalmente mediante diagramas de flujo (flow-chart)
Secuencia
Selección
Iteración
Estructuras anidadas
Expresiones Condicionales
Nivel de Prioridad da las distintas operaciones
Las operaciones se evaluan en el orden en que estan escritas en la expresion, de izquierda a derecha.
Las reglas BNF
Expresión ::= Expresión_simple [Operador_comparador Expresión_simple]
Expresión_simple ::= [ + | - ] Termino {Operador_sumador Termino}
Termino ::= Factor { Operador_multiplicador Factor }
Factor ::= Identificador_de_Variable | Identificador_de_Constante | Valor_Constante | (Expresión) | NOT Factor
Operador_comparador ::= < | > | = | <= | >= | <> | #
Operador_sumador ::= + | - | OR
Operador_multiplicador ::= * | / | DIV | MOD | AND | &
Estructuras básicas en Modula-2
Secuencia
Sentencia IF
IF Condición THEN Secuencia_de_sentencias
ELSIF Condición THEN Secuencia_de_sentencias
ELSE Secuencia_de_sentencias]
END;
Sentencia WHILE
WHILE Condición DO
Acción
END
Sentencia FOR
FOR Variable := Valor_inicial TO Valor_final
[BY Valor_incremento] DO
Secuencia_de_sentencias
END;
Desarrollar programas por refinamientos sucesivos
Esta técnica es parte de las recomendaciones de una metodología general de desarrollo de programas denominada programación estructurada.
Esta técnica consiste en expresar inicialmente el programa a desarrollar como una acción global, que si es necesario se ira descomponiendo en acciones mas sencillas hasta llegar a acciones simples que pueden ser expresadas directamente como sentencias del lenguaje de programación.
Desarrollo de un esquema secuencial:
Para desarrollar una acción compuesta según un esquema secuencial se necesitara:
Metodología de desarrollo de un esquema de selección
Un esquema de selección consiste en plantear una acción compuesta como la realización de una acción entre varias posibles, dependiendo de ciertas condiciones, es decir, se trata de elegir para realizar una sola entre varias posibles alternativas.
Hay que hacer:
Metodología de desarrollo de un esquema de iteración:
Una iteración o bucle consiste en la repetición de una acción o grupo de acciones hasta conseguir el resultado deseado. Para desarrollar un esquema de iteración dentro de un programa deberemos identificar cada uno de sus elementos componentes. Al hacerlo hay que identificar simultáneamente las variables adecuadas para almacenar la información necesaria.
Ejemplos de desarrollo de programas
Un programa es correcto si produce siempre resultados de acuerdo con la especificación del programa. Evidentemente, solo tiene sentido hablar de corrección si antes de escribir el programa se ha escrito de manera precisa la especificación del comportamiento que se espera que tenga.
La única manera de verificar con seguridad la corrección de un programa es demostrar formalmente que el programa cumple con sus especificaciones.
Corrección parcial y total:
La base de la comprobación de corrección parcial es:
Razonamiento sobre sentencias de asignación:
Eficiencia de programas. Complejidad Algorítmica
Medidas de eficiencia
El tiempo que tarda en ejecutarse un programa. La cantidad de memoria usada para almacenar datos
Análisis de programas:
La determinación de la eficiencia de un programa se hace analizando los siguiente elementos
Con el criterio del numero de instrucciones ejecutadas de los esquemas básicos de los programas es el siguiente:
Concepto de subprograma
Un subprograma es una parte de un programa. Es una parte de un programa que se desarrolla por separado y se utiliza invocándolo mediante un nombre simbólico
Se emplea
La técnica de refinamientos sucesivos sugiere descomponer las operaciones complejas de un programa en otras mas simples. En sucesivos pasos de refinamiento, cada operación se vuelve a descomponer hasta que todo el programa se puede escribir utilizando sentencias disponibles en el lenguaje empleado.
Funciones
Una función es un tipo de subprograma que calcula como resultado un valor simple y único a partir de otros valores dados como argumentos.
En líneas generales, una función se asemeja bastante a la idea matemática de función con argumentos.
Definición de funciones:
PROCEDURE Nombre( Argumentos: Tipo; ...) : TipoResultado
Es frecuente que los lenguajes de programación utilicen la palabra PROCEDURE para designar procedimientos, y la palabra FUNCTION para designar funciones. MODULA-2 es un caso especial, y utiliza la palabra PROCEDURE para designar cualquiera de las dos clases de subprogramas.
Estas cabeceras representan la interfaz entre la definición de la función y su utilización posterior. Los nombres de los argumentos son formales, esto quiere decir, que no son variables del programa, sino solo nombres simbólicos que sirven para formalizar la definición posterior de la función, permitiendo hacer referencia a los argumentos en la definición de los cálculos.
Estas cabeceras representan la interfaz entre la definición de la función y su utilización posterior. Los nombres de los argumentos son formales, esto quiere decir que no son variables del programa, sino solo nombres simbólicos que sirven para formalizar la definición posterior de la función, permitiendo hacer referencia a los argumentos en la definición de los cálculos.
La definición completa de una función se compone de la cabecera, seguida de un cuerpo que tiene la misma estructura que un bloque de programa completo. Este bloque comienza con una parte declarativa y continua con una parte ejecutiva introducida con la palabra clave BEGIN. En la parte declarativa se declaran las constantes y variable para el uso local de la función. La parte ejecutiva estará constituida por una secuencia de sentencias. La función finalizara con la palabra clave END y a continuación nuevamente el nombre de la función seguida de un punto y coma ( ; )
En las sentencias que constituyen la función se puede y se debe hacer uso de los argumentos formales declarados en su interfaz. Esto permite parametrizar los cálculos de la función para valores particulares de los argumentos.
Ej:
PROCEDURE Potencia(x: REAL; n: CARDINAL ): REAL;
VAR k : CARDINAL;
p : REAL;
BEGIN
FOR k:= 1 TO n DO
END;
RETURN p
END Potencia;
Se observa que hay una nueva sentencia, iniciada con la palabra clave RETURN. Esta sentencia devuelve como valor de la función el resultado de los cálculos realizados, provoca la finalización inmediata de la ejecución de la función el resultado de la expresión debe ser un valor del tipo indicado en la declaración de la función. Dicho valor es el que se devuelve como resultado de la función.
Estructura:
RETURN Expresión.
Uso de funciones:
Para usar una función en los cálculos de un programa se invoca dicha función escribiendo su nombre y a continuación, entre paréntesis, los valores concretos de los argumentos, separados por comas.
Esta invocación de la función representa un valor del tipo de la función, que podrá ser usado como operando en una expresión aritmética o en cualquier parte del programa en que sea valido escribir una expresión de ese tipo.
Al invocar una función es obligatorio que los valores suministrados para los argumentos correspondan en numero y tipo con los argumentos en la definición. La correspondencia de tipo significa que el tipo del argumento en la invocación sea compatible en asignación con el tipo de argumento formal.
El efecto de la invocación de una función puede describirse en forma simplificada de la siguiente manera
Se evalúan las expresiones de los valores de los argumentos
Funciones predefinidas
En los argumentos simbólicos, X representa un valor numérico, C un carácter, y T un tipo.
Funciones estándar
Las funciones definidas en módulos estándar se denominan funciones estándar y pueden ser utilizadas sin necesidad de escribir su definición, pero hay que indicar expresamente que se van a utilizar dichas funciones mediante una declaración IMPORT del modulo que las contenga.
Todas estas funciones tienen un argumento REAL y devuelven un valor REAL, excepto entier, que devuelve un valor INTEGER.
Procedimientos:
Un procedimiento es un subprograma que realiza una determinada acción. A diferencia de las funciones, un procedimiento no tiene como objetivo devolver un valor obtenido por calculo.
Un procedimiento es una forma de subprograma que agrupa una sentencia o grupo de sentencias que realizan una acción, y permiten darles un nombre por el que se puedan identificar posteriormente.
Estas sentencias se pueden parametrizar mediante una serie de argumentos.
Definición de procedimientos:
La diferencia principal entre procedimiento y función es que no se declara el tipo de valor del resultado, ya que no existe.
En la definición de un procedimiento pueden usarse también sentencias de retorno, pero con un significado algo diferente que en el caso de las funciones. RETURN. Ahora se escribe sin ninguna expresión que lo acompañe, ya que no tiene que devolver ningún valor.
PROCEDURE EscribirResultado;
BEGIN
IF resultado < 0 THEN
RETURN
END;
END EscribirResultado;
Uso de procedimientos:
Nombre (argumento, argumento, ...)
Procedimientos predefinidos:
Los procedimientos EXCL y INCL se comentaran en el tema dedicado al manejo de los conjuntos. El resto tienen la utilidad que se indica. Aquí es también aplicable el comentario hecho acerca de las funciones predefinidas, en cuanto a que los procedimientos predefinidos son en realidad seudoprocedimientos que forman parte del lenguaje en si.
Procedimientos estándar
Paso de Argumentos:
La manera fundamental de comunicar información entre las sentencias de un subprograma y el programa que lo utiliza es mediante argumentos. Hay 2 formas:
PROCEDURE Nombre (VAR argumento: Tipo ...) ...
Visibilidad. Estructura de bloques:
Un subprograma se define de la misma forma que un programa completo, y puede contener declaración de constantes, variables y otros subprogramas.
Dichos elementos tienen un sentido local, de manera que no son accesibles desde el exterior del bloque.
La utilización de las variables globales es otra manera en que un subprograma puede producir resultado, asignando valores directamente a cualquiera de las variables del programa principal que lo utiliza.
Problemas de uso
Redefinición de elementos
El empleo de elementos diferentes con el mismo nombre aumenta la complejidad del programa y se dificulta mucho su comprensión. Además, se abre una vía de errores no detectables en compilación. Aunque se pretenda utilizar un símbolo como local, si se olvida su redefinición, se asumirá incorrectamente el significado dado como símbolo externo. Por tanto, salvo que sea imprescindible no se debe utilizar la redefinición de elementos.
Efectos secundarios
Cuando un subprograma modifica alguna variable eterna, se dice que esta produciendo efecto secundarios o laterales. El uso de subprogramas con efectos secundarios debe hacerse con precaución.
Para utilizar el subprograma solo se necesita conocer su interfaz y a partir de ella establecer los posibles resultados que se pueden producir. Cuando un subprograma produce efectos secundarios, esta incumpliendo el compromiso establecido en su interfaz. Esto dificulta la compresión del subprograma y puede producir resultados inesperados.
Solo si el numero de argumentos necesarios en un subprograma es muy grande o en situaciones que requieran el paso en los argumentos de grandes cantidades de datos esta justificado la utilización de efectos secundarios.
Si un subprograma produce efectos secundarios, estos deben indicarse explícitamente en los comentarios de su cabecera.
Se entiende por funcionalidad la cualidad que poseen aquellos subprogramas que permiten asegurar que siempre que se llamen con los mismos argumentos producirán exactamente los mismos resultados. Esto se logra evitando la utilización de efectos secundarios. Esa cualidad también se denomina transparencia referencial.
Doble referencia:
Se produce este efecto cuando un mismo elemento se referencia con dos nombres distinto. Puede ocurrir:
Operaciones abstractas
Una abstracción es una visión simplificada de una cierta entidad, de la que solo consideramos sus elementos esenciales, prescindiendo en lo posible de los detalles. Las entidades que podemos abstraer para materializarlas como subprogramas son operaciones. Con la palabra operación englobamos tanto la idea de acción como la de función.
Especificación y realización
Hay que definir 2 posibles visiones.
Resumiendo:
La forma simplificada de especificación indica solamente cual ha de ser la sintaxis o forma de uso de la operación. La especificación completa debe establecer también cual es la semántica o significado de la operación.
Funciones. Argumentos
La idea de función surge al aplicar el concepto de una abstracción a las expresiones aritméticas. Una expresión representa un nuevo valor obtenido por calculo a partir de ciertos valores ya conocidos que se usan como operandos.
El concepto matemático de función es una aplicación entre conjuntos, cuyo computo se limita a suministrar un resultado, son modificar el valor de los argumentos.
La transparencia referencial se garantiza si la realización de la función no utiliza datos exteriores a ella. Es decir, si no emplea
Esta restricciones se cumplen en el ejemplo anterior del calculo del cubo de un numero. Las funciones que cumplen la cualidad de transparencia referencial y que no producen efectos laterales o secundarios se denominan funciones puras
Acciones abstractas. Procedimientos
Un procedimiento representa una acción, que se define por separa y que se invoca por su nombre.
Al definir procedimientos no podemos limitarnos a usar solo el paso de argumentos por valor. En programación imperativa las acciones consisten habitualmente en modificar los valores de determinadas variables. Por esta razón se considera normal que los procedimientos usen argumentos pasados por referencia.
Los procedimientos se escriben siempre como procedimientos puros, entendiendo por ello que no produzcan efectos laterales o secundarios. Con esto se consigue que la acción que realiza un procedimiento se deduzca en forma inmediata de la invocación de dicha acción.
Un procedimiento cumple esta cualidad si su realización no utiliza:
Desarrollo por refinamiento usando abstracciones
Desarrollo descendente
Es simplemente el desarrollo por refinamientos sucesivos, teniendo en cuenta además la posibilidad de definir operaciones abstractas.
Considerar la operación como operación terminal, y codificarla mediante sentencias del lenguaje de programación
Considerar la operación como operación compleja, y descomponerla en otras mas sencillas.
Considerar la operación como operación abstracta, y especificarla, escribiendo mas adelante el subprograma que la realiza.
En general resultara ventajoso refinar una operación como operación abstracta, que se define en forma separada, si se consigue alguna de las ventajas siguientes:
La llamada al subprograma representa una acción adicional que consume un cierto tiempo de ejecución.
Reutilización
La realización de ciertas operaciones como subprogramas independientes facilita la reutilización de software.
La escritura de otros programas que utilicen esa misma operación resulta mas sencilla, ya que se aprovecha el código de su definición que ya estaba escrito.
Para aplicar de manera eficaz esta técnica, es preciso pensar en las posibles aplicaciones de un cierto subprograma en el momento de especificarlo, con independencia de las necesidades particulares del programa que se esta desarrollando en ese momento.
Esto tiene ventajas e inconvenientes:
Desarrollo ascendente
Esta metodología consiste en ir creando subprogramas que realicen operaciones significativas de utilidad para el programa que se intenta construir, hasta que finalmente sea posible escribir el programa principal, de manera relativamente sencilla, apoyándose en los subprogramas desarrollados hasta ese momento.
Programas robustos
La corrección de un programa exige que los resultados sean los esperados, siempre que el programa se ejecute con unos datos de entrada aceptables.
Un programa se dice que es robusto si su operación se mantiene en condiciones controladas aunque se le suministren datos erróneos.
Programación a la defensiva
Consiste en que cada programa o subprograma este escrito de manera que desconfíe sistemáticamente de los datos o argumentos con que se le invoca, y devuelva siempre como resultado:
La mejora de la robustez del programa tiene como contrapartida una cierta perdida de eficiencia, al tener que hacer comprobaciones adicionales.
Tratamiento de excepciones
Ante la posibilidad de errores en los datos con que se opera, hay que considerar dos actividades diferentes:
Definición de tipos
Mediante la definición de tipos de datos se consigue que cada información que maneja el computador tenga su sentido especifico. El tipo establece los posibles valores que puede tomar ese dato. La definición de tipos supone crear un nuevo nivel de abstracción dentro del programa.
En Modula-2 la declaración se realiza junto a la declaración de las constantes y variables, dentro de la 'parte_declarativa' de cualquier Bloque. Esta declaración se inicia con la palabra lave TYPE.
TYPE TipoEdad = INTEGER; TipoSexo = CHAR;
Cada tipo se define mediante un nombre o identificador seguido del símbolo igual (=), y a continuación la descripción concreta del tipo que se quiere definir. Las diferentes definiciones de tipos se separan mediante punto y coma (
. En la definición de un nuevo tipo puede utilizar y normalmente utiliza otras tipos ya definidos. De manera formal, la sintaxis y la ubicación de la declaración de tipos:
Bloque ::= Parte_declarativa Parte_ejecutiva END Parte_declarativa::={Declaración} Declaración ::= Declaración_de_constantes | Declaración_de_tipos
| Declaración_de_variables | Declaración_subprograma
Declaración_de_tipos ::= TYPE {Definición_de_tipo ;}
Definición_de_tipo ::= Identificador =Esquema_de_tipo Esquema_de_tipo ::= Identificador_de_tipo | Tipo_enumerado | Tipo_subrango | Tipo_conjunto
Tipos enumerados
Definición de tipos enumerados
Una manera de definir un nuevo tipo de dato es enumerar todos los posibles valores que puede tomar. Se realiza mediante una lista separada por comas (,) y entre paréntesis. Cada posible valor se describe mediante un identificador. Estos identificadores al mismo tiempo quedan declarados como valores constantes. La enumeración implica un orden que se establece entre los valores enumerados. este orden se define de forma implícita e impone que el primer elemento de l lista ocupa el lugar 0 hasta el ultimo que ocupa el N-1. ( N = numero de elementos enumerados). Los tipos de datos enumerados forman parte de una clase de tipos de MODULA-2 denominados tipos ordinales, a la cual pertenecen también los tipos INTEGER, CARDINAL y CHAR, pero no el tipo REAL.
Tipo_enumerado ::= (Lista_de_identificadores) Lista_de_identificadores ::= Identificador {, Identificador}
Uso de tipos enumerados
Los tipos enumerados se emplean de manera similar a los tipos predefinidos. Los identificadores de tipo se puede emplear para definir variables de ese tipo y los identificadores de los valores se emplean como las constantes con nombre. Puesto que entre los valores enumerados existe un orden definido, podemos emplear con ellos los operadores de comparación. Al igual que para el resto de los ordinales, podemos utilizar la función predefinida ORD para obtener la posición en la lista de valores del tipo. La operación inversa, se consigue mediante la función VAL (T, N) y devuelve el valor que ocupa la posición N en la colección de valores del tipo T. Otras operaciones aplicables corresponden a procedimientos predefinidos INC y DEC, que reemplazan un valor por el siguiente o anterior.
Tipo predefinido Boolean
TYPE BOOLEAN = (FLASE, TRUE) ORD (FALSE) = 0 ORD (TRUE) =
1
Es posible realizar operaciones lógicas entre operandos booleanos (variables o no)
Operador Operación Lógica AND Conjunción OR Disyunción NOT
Negación
Es bastante habitual definir funciones cuyo resulta es un valor booleano, cuando se quiere realizar un test sobre argumentos de la función se le llama predicas: ODD (2) = FALSE ODD (7 )= TRUE
Tipo subrango
Definición de tipos subrango
Un tipo de subrango se define a partir de otro tipo ordinal ya definido, que se toma como tipo base. La forma de realizar esto
es declarar un identificador diferente para el nuevo tipo y establecer los limites mínimo y máximo del subrango de variación. Sobre el tipo real no es posible definir ningún subrango, debido a que no es tipo ordinal.
Tipo_Subrango := [Identificador_de_tipo][Limite_inferior .. Limite_superior]
Limite_inferior ::= Expresión_constante Limite_superior ::= Expresión_constante
Uso de tipos subrango
Las variables de un tipo subrango tienen la misma consideración
que las variables tipo base. La ventaja principal que ofrecen el tipo de subrango es que, previamente a cualquier asignación a una variable, se puede comprobar automáticamente que el valor a asignar esta dentro de los limites declarados, si dicho valor esta fuera, el programa finaliza con error.
Tipos estructurados
Todos los tipos de datos presentados hasta ahora se denominan escalares y son datos simples, en el sentido de que no se pueden descomponer. Un tipo estructurado de datos es un tipo cuyos valores se construyen agrupando datos de otros tipos mas sencillos Los elementos de información que integran un valor estructurado se denominan componentes.
Estructuras complementarias de iteración
Sentencia REPEAT
Resulta adecuado emplear este esquema cuando solo son validos unos valores concretos para una determinada respuesta.
REPEAT
sentencias
UNTIL condición;
LOOP y EXIT
Indica que se ejecute siempre de forma repetitiva e incondicional la serie de sentencias
LOOP
sentencias
END;
EXIT
La sentencia EXIT es incondicional, es decir, no contiene en si misma el examen de ninguna condición.
Una sentencia EXIT SOLO se puede usar dentro de otra tipo LOOP y se produce un error de compilación cuando se trata de usar fuera de LOOP.
Estructuras complementarias de selección
Sentencia CASE
CASE valor OF
valor1 : accion1 |
valor2 : accion2 |
ELSE acción por defecto
END;
Necesidad de las formaciones
Facilita la ordenación de diversos datos. Las formaciones permiten la generalización de la declaración, referencia y manipulación de datos del mismo tipo.
Vectores
Declaración de vectores: TipoVector = ARRAY TipoIndice OF TipoElemento
Operaciones con vectores
Formaciones anidadas. Matrices
Declaración de matrices:
TipoMatriz = ARRAY TipoIndice1, TipoIndice2,... OF TipoElemento
Operaciones con matrices
Esquemas típicos de operación
Recorrido
Consiste en realizar la misma operación con todos y cada uno de los elementos de una formación Se emplean estructuras de tipo FOR Las variables utilizadas como indices de los FOR deben ser de los mismos TipoIndice utilizados en la declaración de la formación y su rango de variación será desde un valor extremo al otro del rango de la misma declaración
Búsqueda secuencial
El recorrido se debe detener en cuanto se encuentre el elemento buscado y solo será completo cuando no se encuentre el elemento buscando dentro de la formación Se emplean estructuras WHILE.
Esquema general:
Iniciar Búsqueda
WHILE (NOT encontrado) AND (NOT final)
DO
Pasar al siguiente elemento;
Verificar encuentro;
END
Cuando se sabe que el vector tiene al menos un elemento, es posible emplear la sentencia REPEAT, también se puede utilizar la secuencia LOOP
Inserción
Una operación de inserción de un nuevo elemento en una formación supone desplazar algunos de sus datos actuales para conseguir el hueco que le corresponde al nuevo elemento y después insertarlo en el hueco
Posible esquema:
Iniciar Inserción
WHILE (NOT hueco_encontrado) AND (NOT final)
DO
Desplazar elemento;
Pasar al siguiente elemento;
END;
Insertar nuevo elemento
Ordenación por inserción directa
Este modo consiste en insertar el elemento extraído en su lugar correspondiente entre los elementos ya ordenados Se repite con todos los vectores hasta quedar ordenado todo el vector
Búsqueda por dicotomía
Consiste en buscar un elemento en 2 mitades La búsqueda finaliza cuando el elemento se encuentra o bien cuando no es ninguno de los 2 datos consecutivos a los que queda reducida la comparación después de sucesivas divisiones en mitades.
Simplificación de las condiciones de contorno
Matrices orladas: Matrices que se dimensionan con 2 filas y 2 columnas mas de las necesarias
Vector de caracteres: Ristra
Las ristras son vectores de caracteres: ARRAY [0..n] OF CHAR: considerado como ristra o string.
Características peculiares de las ristras:
Argumentos de tipo vector abierto
Una forma de trabajar con vectores de diferentes tamaños es definir un tipo único con capacidad para el mayor de todos los que se necesiten
Cuando no se utiliza todo el tamaño, es necesario indicar de alguna forma que elementos se están empleando realmente.
Esto se puede hacer marcando el final con un valor nulo como sucede con las ristras (despilfarra memoria). La solución es emplear vectores abiertos.
Vector abierto: Es aquel del que solo se define el tipo de sus elementos, pero no el rango del indice. El tipo implícito de su indice es siempre un subrango comprendido entre 0 y 1, valor máximo.
PROCEDURE Muestra (X: ARRAY OF TipoElemento)
El esquema de tupla
Los tipos registro
Definición de registro
RECORD nombre: TIPO;
nombre: TIPO;
END
Define un campo o elemento componente. Puede usarse para definir el tipo de registro como un tipo con nombre o descripción del tipo de una variable.
Uso de registros
La sentencia WITH
Formato
WITH registro DO
secuencia_de_sentencias
END;
En la secuencia de sentencias se puede hacer referencia a los campos del registro escribiendo solo su nombre.
Las sentencias WITH pueden anidarse. Al hacerlo hay que tener en cuenta que su los registros tienen campos con los mismos nombres, se presenta una situación de ambigüedad.
Estructuras combinadas
Se pueden definir estructuras cuyas componentes son a su vez estructuras, sin limite de complejidad de los esquemas de datos resultantes.
Formas de Combinacion
El esquema unión
A veces es deseable que el tipo de un dato varíe según las circunstancias. Si las posibilidades son finitas podremos decir que el tipo de dato corresponde a un esquema que es la unión de los tipos particulares posibles.
tipo_unión = variante | variante2 | variante3 ...
Podemos aplicar los esquemas de unión a
Registros con variantes
Los esquemas de unión pueden utilizarse en Modula-2 definiéndolos como registros con variantes.
Un registro con variante tiene unos campos fijos mas una colección de variantes, cada una de las cuales consiste en un grupo particular de campos.
Para almacenar explícitamente la información de la variante concreta que se esta usando utilizamos un campo fijo discriminante (TAG)
Definición de registros con variantes
RECORD
campos_fijos
CASE discriminante : tipo OF
valor : variante |
valor : variante |
...
ELSE
variante
END;
END;
Uso de registros con variantes
Los registros con variantes se utilizan de la misma manera que los registros sin variantes.
La forma de hacer referencia al campo discriminante o a los campos de las variantes es igual que para los campos fijos.
Resumen de reglas para registro
Definición de registro
Tipo_Registro::= RECORD Secuencia_de_lista_de_campos END
Secuencia_de_lista_de_campos ::= Lista_de_campos {Lista_de_campos}
Lista_de_campos::= [Lista_de_verificadores:Esquema_de_tipo
| parte_variante]
parte_variante::= CASE [identificador :] Identificador_de_tipo OF variante {| variante}
[ELSE secuencia_de_listas_de_campos]
END
variante::= Lista_de_valores: Secuencia_de_listas_de_campos
Referencias a variables o componentes
Variable::= (Identificador_de_variable | identificador_de_campo)
{. Identificador_de_campo| [Lista_de_expresiones]}
Esquemas de datos y esquemas de acciones
Estructuras de datos no acotadas
Disponemos de estructuras de datos que no tuvieran un tamaño fijado de antemano, sino que pudieran ir creciendo o reduciendo su tamaño en función de los datos particulares que se estén manejando en cada ejecución del programa.
Estas estructuras de datos se denominan estructuras dinámicas y poseen la cualidad de que su tamaño es potencialmente ilimitado, aunque no podrá exceder la capacidad física del PC.
La estructura secuencia
Puede definirse como un esquema de datos del tipo iteración, pero con un numero variable de componentes.
Es parecida a una formación con numero variable de elementos.
Existen distintas alternativas
Variables dinámicas
Una variable dinámica no se declara como tal, sino que se crea en el momento necesario y se destruye cuando no se necesita
No tienen nombre, sino que se designan mediante otras variables
llamadas puntero o referencias.
Punteros
TYPE tipo_puntero = POINTER TO Tipo_de_variable;
Uso de variables dinámicas
TYPE Tipo_puntero = POINTER TO Tipo_de_dato;
VAR pun : Tipo_puntero;
NEW (pu) = ALLOCATE (pun,size (Tipo_dato))
DISPOSE (pun) = DEALLOCATE (pun, size(Tipo_dato))
Realización de secuencias mediante punteros
Al utilizar los punteros, debes tener cuidado y emplearlo
de manera precisa, traduciendo a punteros los mecanismos de definición de alto nivel que deberían estar disponibles.
Una definición recursiva es aquella en que se hace referencia
a si misma.
Operaciones con secuencias enlazadas
Describimos la manera de realizar algunas operaciones típicas sobre secuencias enlazadas con punteros
Recorrido: El recorrido de toda secuencia se consigue mediante un bucle de acceso a elementos y avance del cursor
Como la secuencia tiene un numero indefinido de elementos, no se usara un bucle con contador.
Cursor := secuencia;(*iniciar cursor*)
WHILE cursor <> NIL DO (* NOT fin*)
WriteInt (cursor^.valor,10)
cursor := cursor^.siguiente(* avanzar cursor *)
END
Datos persistentes
Son aquellos que conservan su valor entre ejecuciones sucesivas de los programas que operan con ellos.
Intervienen la memoria interna y la externa (CD´s, Diskettes,...)
Este tipo de datos esta ligado a las operaciones de entrada y salida de información
Para manejar datos persistentes hay que declarar variables en las que serán copiados temporalmente (durante el funcionamiento del programa) y ademas se necesitan operaciones adicionales para establecer la conexión entre dichas variables en memoria interna y los datos en memoria externa.
Ficheros
La forma clásica de usar datos persistentes es mediante el
empleo de ficheros
Los ficheros son estructuras de datos en memoria externa,
que pueden ser manipulados desde un programa, estableciendo una conexión entre dichos ficheros en memoria externa y las variables fichero definidas por el programa en memoria interna.
Los ficheros son estructuras de datos no acotadas, que pueden
almacenar tanta información como se desee, dentro de la capacidad real disponible en cada soporte.
Los mas sencillos son un esquema de datos del tipo secuencia
Un sistema de ficheros permite tener almacenados en un mismo soporte diferentes ficheros que se reparte entre ellos la capacidad de almacenamiento disponibles.
Cada fichero ocupa en un momento dado el espacio que necesita, en funciona de la cantidad de información que contiene, ya que los ficheros son estructuras dinámicas de datos
Los nombres de los distintos ficheros han de ser usados como parámetros en las operaciones de apertura de ficheros.
Ficheros secuenciales
Son estructuras de tipo secuencia:
Si es necesario modificar parte del fichero, hay que generar
otro nuevo con la información actualizada a partir del antiguo.
Si el proceso termina con éxito puedes borrar el antiguo y quedarte con el nuevo
Los ficheros de entrada y salida principales son manejados por el modulo estándar InOut.
Cada fichero se utiliza mediante un curso implícito, que se maneja indirectamente mediante los procedimientos de lectura y escritura.
Operaciones básicas:
El modulo InOut permite cambiar durante la ejecución del programa los ficheros asignados como entrada y salida principales.
Para ello existen unos procedimientos predefinidos llamados 'OpenInput' y 'OpenOutput', que provocan la lectura por el terminal de los nombres de los ficheros correspondientes.
También existen los procedimientos 'Closeinput' y 'CloseOutput'
que finalizan el trabajo con dichos ficheros y vuelven a emplear la entrada y salida principal establecida al comienzo del programa.
Ficheros de texto
Es en realidad un fichero secuencial de caracteres
La única diferencia esta en el modelo lógico de esa secuencia
de caracteres, que se considera dividida en lineas de texto, separadas unas de otras por caracteres especiales de fin de linea.
La organización del texto en lineas permite estructurar los datos de entrara y salida, aprovechando los separadores de lineas para marcar la separación de los grupos de datos.
Para detectar los cambios de linea en el fichero de entrada del modulo InOut exporta una constante EOL (End Of line) de tipo carácter.
Si el carácter leído es igual a 'EOL' es que se ha saltado de linea
Lectura y escritura con conversión
Los ficheros de texto son el medio principal de combinación
entre el usuario y la maquina aplicamos este termino a cualquier medio de entrada o salida de secuencias de caracteres, incluyendo los terminales con pantalla y teclado...
La ventaja de los ficheros de texto es que prácticamente cualquier elemento de información puede representarse en forma legible mediante ristras de caracteres.
La lectura o escritura de estos valores a partir de ficheros de texto exige la conversión entre el valor interno, en el código de la maquina y su representación externa en forma de secuencia de caracteres.
Los procedimientos de lectura standard ReadInt', 'ReadCard'
y 'ReadReal'. Tienen el siguiente comportamiento:
Estas operaciones no son procedimientos predefinidos
Ficheros de acceso directo
Se denominan ficheros de acceso directo aquellos en que se puede acceder directamente a un elemento de información contenido en el fichero, sin necesidad de acceder a los anteriores.
Existen varios modelos lógicos diferentes de ficheros de acceso directo:
Solo estudiamos ficheros con organización relativa, también llamados 'Ficheros de acceso directo'
Las características de las operaciones desde acceso son:
El manejo de ficheros de acceso directo en Modula-2 no esta del todo normalizado. Existe un modulo estándar llamado FileSystem, que contiene las operaciones adecuadas para estos ficheros.
Concepto de módulo
Un modulo es un fragmento de programa independiente utilizado en algún momento para la construcción del programa completo.
Un modulo debería ser compilado por separado. La razón se debe a la necesidad de limitar la complejidad de aquello que esta siendo elaborado por una persona en un momento dado.
El concepto de modula esta ligado a la idea de abstracción. Un modulo debe definir un elemento abstracto y debe ser usado desde fuera con solo saber que hace el modulo pero sin saber como lo hace.
Especificación y realización
La especificación de un modulo que contenga la definición de una serie de elementos abstractos consiste en el conjunto de las
especificaciones de cada uno de ellos mas una indicación de los posibles efectos de unos sobre otros cuando se usan en forma combinada.
Es todo lo necesario para poder usar los elementos definidos en el. Constituye el interface entre el modulo y el programa que lo usa.
La realización del modulo consiste en la realización de cada
uno de los elementos abstractos contenidos en dicho modulo.
La independencia entre la realización de un modulo y el programa
que lo usa se incrementa si la realización de un elemento abstracto no es visible desde donde se usa. Esta característica se denomina ocultación (mecanismo de visibilidad de los bloques)
Técnicas empleadas por lenguajes de programación reales
Descomposición modular
Esta descomposición permite trabajar a distintos programadores en el mismo programa.
El objetivo de la ingeniería de software es facilitar el desarrollo de la aplicación en forma organizada. Para que la descomposición en módulos sea adecuada, los módulos tienen que ser completamente independientes. Esta independencia se analiza según el acoplamiento y cohesión.
Módulos en Modula-2
Modulo principal
La ejecución del programa equivale a ejecutar este modulo.
A partir de este, se pueden importar los demás módulos.
Módulos de definición
La especificación de un modulo se escribe como modulo de definición:
DEFINITION MODULE Nombre;
...lista de importaciones...
...definiciones...
END Nombre.
Las listas de importaciones se escriben igual que para el modulo principal.
Las definiciones pueden incluir definiciones de constantes, tipos y variables, también pueden incluir especificaciones de subprogramas.
Una especificación de subprograma consistirá en la cabecera de la función o procedimiento.
La lista de exportación solo se emplea en versión antiguas de Modula-2, en las versiones actuales, todos los elementos del modulo de definición se exportan implícitamente.
Módulos de implementación
Contienen la realización del modulo.
IMPLEMENTATION MODULE Nombre;
...lista de importaciones...
...definiciones...
BEGIN
...sentencias de inicializacion...
END Nombre.
Las sentencias ejecutables que figuran al final del modulo son opcionales. Estas sentencias se ejecutan automáticamente al comienzo de la aplicación, antes de que se ejecute el programa principal, y antes de que se ejecute la inicialización de cualquier otro modulo que utilice este modulo.
Según la jerarquía de módulos reflejada en el diagrama de estructura de una aplicación, comprobamos que se ejecutan estrictamente de abajo a arriba, empleando por inicializar los módulos que no usan a ningún otro, luego los que usan estos, y terminando por ejecutar el modulo principal.
Uso de módulos
Los elementos definido en un modulo se pueden usar desde otro. Para ello hay que importarlos. La importación se realiza al comienzo del programa o modulo que desea usarlos.
Formas para importar
Tipos abstractos de datos
Un enfoque mas moderno de la programación trata de asociar la idea de tipo de datos con la clase de valores, abstractos, que
pueden tomar los datos.
Los valores de los tipos enumerados no son valores numéricos, ni ristras de caracteres, aunque pueden transformarse en esas otras formas.
En este enfoque, llegamos a la idea de tipos abstractos, esto quiere decir que un programa que use ese tipo de datos no debería necesitar ningún cambio por el hecho de modificar la representación o codificación de los valores de ese tipo. Si analizamos con cuidado que necesita un programa para poder usar datos de un tipo, encontraremos que hace falta:
El conjunto de todos estos elementos constituye el tipo abstracto de datos (Agrupación de una colección de valores y una colección de operaciones de manipulación)
Desarrollo modular basado en abstracciones
Abstracciones para desarrollo modular
Las abstracciones se llaman abstracciones funcionales, con independencia de que sean una función propiamente dicha o bien un procedimiento que realiza una acción.
El mecanismo de módulos permite ampliar el repertorio de abstracciones, a las que se les denominan abstracciones de datos y por demos distinguir, los tipos abstractos de datos y los datos abstractos (encapsulados)
Desarrollo por refinamiento basado en abstracciones
La única novedad que se introducen ara son las abstracciones
de datos y la posibilidad de desarrollar también las abstracciones funcionales como módulos separados.
Reutilización de módulos
Se suele considerar que la descomposición modular basada en abstracciones es una buena metodología para desarrollar módulos
que van a ser reutilizados en el futuro.
Los módulos que definen abstracciones relacionadas entre si pueden agruparse en una biblioteca que se pone a disposición de quienes desarrollan aplicaciones en un campo determinado.