Descargar

Tópicos de Lenguajes de programación (página 2)


Partes: 1, 2, 3

2. Especificación de sintaxis y semántica

SINTAXIS Un programa en cualquier lenguaje se puede concebir como un string de caracteres escogidos de algún conjunto o alfabeto de caracteres. Las reglas que determinan si un string es un programa válido o no, constituyen la sintaxis de un lenguaje. Posteriormente, se estudiarán ciertas notaciones denominadas expresiones regulares y gramáticas libres de contexto, muy usadas no sólo para especificar las sintaxis de los lenguajes de programación sino también para contribuir en la construcción de sus compiladores.

SEMÁNTICA Las reglas que determina el significado de los programas constituyen la semántica de los lenguajes de programación. Es más difícil de especificar que la sintaxis.

Los siguientes son algunos enfoques para especificar la semántica de los lenguajes:

Semántica Interpretativa (u Operacional)

Un lenguaje de máquina tiene su semántica definida por el computador. Un programa en lenguaje de máquina "significa" exactamente lo que el computador hace cuando el programa "corre" o se ejecuta. Sin embargo, con un lenguaje de alto nivel no se puede dejar que el computador defina la semántica del lenguaje, puesto que no es posible "correr programas y ver" hasta que se tenga un compilador. No se puede tener un compilador y saber qué es correcto hasta haber definido lo que los programas significan. Este enfoque interpretativo para definir la semántica de los lenguajes de programación consiste en postular una máquina abstracta y proveer reglas para la ejecución de programas sobre esta máquina abstracta. Así, estas reglas definen el significado de los programas. Usualmente, la máquina abstracta se caracteriza por un estado consistente de todos los objetos datos, sus valores y los programas con sus contadores de programa. Las reglas semánticas especifican cómo el estado es transformado por las diversas construcciones de los lenguajes de programación.

Traducción La traducción de un lenguaje assembly a lenguaje de máquina (que es directa y comprensible), forma una especificación semántica muy usada para un lenguaje assembly.

El compilador para un lenguaje de alto nivel sobre una máquina específica llega a ser la definición semántica del lenguaje.

Definición Axiomática

Se pueden definir reglas que relacionan los datos antes y después de la ejecución de cada programa. Estas reglas se pueden usar para proveer teoremas acerca de la relación E/S de un programa. Este enfoque tiene la ventaja que puede usarse para definir semánticas para un aparte más que para todos los aspectos de un lenguaje.

Definición Extensible

En este enfoque se definen ciertas operaciones primitivas y el significado del lenguaje en términos de estas primitivas. Ejemplo LISP.

Semántica Matemática o Denotacional

Los objetos matemáticos corresponden a programas que son definidos y reglas abstractas para traducir programas a estos objetos abstractos.

LA ESTRUCTURA JERÁRQUICA DE LOS LENGUAJES DE PROGRAMACIÓN

Un lenguaje de programación es una notación para especificar una secuencia de operaciones a realizar sobre objetos dados. Estos se pueden agrupar en una jerarquía de árbol, cuyas unidades son comunes y familiares a la mayoría de los lenguajes.

En el tope de la jerarquía está el programa propiamente tal, que es la unidad de ejecución básica. Luego viene una entidad que puede ser compilada y que se compone de sentencias y expresiones.

LENGUAJES DE ALTO NIVEL

Un lenguaje de programación es un medio para comunicar algoritmos al computador. Existen cientos de lenguajes de programación los cuales difieren en el grado de clausura a un lenguaje natural o matemático por una parte, y al lenguaje de máquina por otra. También difieren en el tipo de problema para el cual funcionan mejor. Algunos aspectos de los lenguajes de alto nivel que los hacen preferibles a los lenguajes de máquina o assembly son:

Facilidad de Entender. Un programa en lenguaje de alto nivel es: fácil de leer y de escribir la notación más natural de describir algoritmos.

Un buen lenguaje de programación debería proveer aspectos para el diseño modular contemplando operadores, estructura de datos, flujo de control.

Naturalidad. Facilidad de poder expresar un algoritmo en el lenguaje (sentencias estructuradas, estructura natural del algoritmo)

Portabilidad. Posibilidad de ejecutar programas en una variedad de máquinas.

Eficiencia de Uso. Considerar aspectos del lenguaje como de los programas. Compilación eficiente.

Existen aspectos que hacen más legibles los programas en lenguajes de alto nivel:

Estructuras de Datos. Reglas de Alcance que permitan modificaciones en partes de un programa sin afectar otras partes del mismo.

Flujo de Control que identifique claramente las estructuras de ciclo de un programa Subprogramas que permitan diseño modular.

Un compilador también puede ayudar a producir programas confiables. Por ejemplo, un compilador puede chequear si los tipos de operandos son compatibles; puede advertir al usuario si hay posibilidad de usar una variable antes de ser definida, o si una referencia a un arreglo puede estar fuera de los límites.

ELEMENTOS SINTÁCTICOS DE UN LENGUAJE DE PROGRAMACIÓN Conjunto de Caracteres

Es la primera etapa en el diseño de la sintaxis de un lenguaje. Casi todos tienen el mismo conjunto de letras y dígitos. La diferencia está en los caracteres especiales Incide en la determinación del tipo de equipo para la E/S

Identificadores Sintaxis básica: string de letra y/o dígito comenzando con letra

Se introducen caracteres especiales que facilitan la lectura

Operadores La mayoría de los lenguajes tiene un conjunto estándar de operadores: aritméticos relacionales lógicos Palabras Clave y Reservadas

Palabra clave: identificador usado como parte fija de la sintaxis de una sentencia. Palabra reservada: palabra clave que no puede ser usada como identificador.

El análisis sintáctico se facilita usando palabras reservadas

Generalmente una sentencia comienza con palabras claves designando el tipo de sentencia.

Comentarios y Palabras Opcionales

La mayoría de los lenguajes permite incluir comentarios en los programas Los comentarios no son considerados durante la compilación.

Espacios en Blanco

Varía su inclusión entre un lenguaje y otro

Delimitadores Es un elemento sintáctico para marcar el comienzo y/o el fin de una unidad sintáctica

Ejemplo

BEGIN …. END Las características son: realza la lectura simplifica el análisis sintáctico elimina ambigüedades delimita explícitamente frontera o cota de una construcción sintáctica.

Formato Libre y Fijo

Una sintaxis es libre si las sentencias del programa se pueden escribir en cualquier parte de una línea sin observar la posición o salto de línea. Ejemplo Pascal

Una sintaxis es fija si utiliza la posición en la línea para proporcionar información.

Ejemplo estrictamente fija, máquina; parcialmente fija, Fortran

Expresiones Son unidades sintácticas básicas con las cuales se construyen sentencias. Hay distintas formas para escribir una expresión: infija, prefija, postfija (inorden, preorden, postorden).

Sentencias Son las componentes sintácticas más importantes de los lenguajes de programación. Algunos tienen formato básico de sentencia. Existen diferentes sintaxis para cada tipo diferente de sentencia.

En general existen sentencias simples y estructuradas.

Estructura de Programa y Subprograma

La organización sintáctica de un programa y la definición de subprograma es variada.

Definición variada de subprograma

  • cada definición de subprograma es tratada como una unidad sintáctica separada
  • Las principales estructuras de los programas son: bloques, subprogramas
  • El bloque se ejecuta cuando la secuencia de ejecución llega. El procedimiento es llamado explícitamente.

En Fortran cada subprograma es compilado separadamente y los programas son "linkeados" en tiempo de carga.

En APL los subprogramas son compilados separadamente y son "linkeados" sólo cuando uno llama a otro durante la ejecución.

Definición de subprogramas anidados

Un ejemplo se da en ALGOL que muestra la estructura de programas anidados en que la definición del subprograma aparece como declaración en el programa principal. A su vez, el mismo subprograma puede contener otra definición anidada.

Descripción separada de datos y sentencias ejecutables

Un representante es el COBOL. Las declaraciones de datos y sentencias ejecutables para los subprogramas (párrafos), se encuentran en la DATA DIVISION y en la PROCEDURE DIVISION, respectivamente. La ENVIRONMENT DIVISION consiste de las declaraciones de operación y ambiente externo, equipo, archivos, memoria, etc… Todos los datos son globales a los subprogramas. Una ventaja es que cambios en la estructura de datos se hacen sin tocar los subprogramas.

Definición de subprogramas no separados

No hay diferencia sintáctica entre las sentencias del programa y las del subprograma. Un ejemplo SNOBOL: independiente del número de subprogramas que contenga el programa, es una lista de sentencias. El punto donde un subprograma comienza y termina no se distingue. Los programadores introducen una separación artificial insertando comentarios. Otro ejemplo BASIC.

3. Tipos de datos, alcance y tiempo de vida de variables

  • Datos

Dato: "hecho o valor a partir del cual se puede inferir una conclusión; información"

Los datos son aquello que un programa manipula. Sin datos un programa no funcionaría correctamente. Los programas manipulan datos de manera muy diferente según el tipo de dato del que se trate. Y hay varios de estos tipos:

  • Tipos de Datos
  • Cadenas de Caracteres

Ya hemos visto este tipo de datos. Son literalmente cualquier cadena o secuencia de caracteres que puedan imprimirse en la pantalla. (De hecho pueden ser también ciertos caracteres de control que no son imprimibles).

En Python las cadenas pueden representarse de varias formas:

Con comillas simples:

'Esta es una cadena'

Con comillas dobles:

"Esta otra es una cadena muy similar"

Con tres comillas dobles:

""" Esta es una cadena muy larga que puede

Ocupar varias líneas si así lo deseamos y

Python la mantendrá del mismo modo en que

Un uso especial de esta última forma puede verse en la generación de la documentación de las funciones de Python creadas por nosotros mismos, algo que veremos más adelante.

Podemos acceder a los caracteres individuales de una cadena al tratarla como una matriz de caracteres. Usualmente hay varias operaciones que el lenguaje de programación provee para ayudarnos a manipular las cadenas, tales como buscar una subcadena dentro de otra, unir dos cadenas, copiar una cadena en otra, etc.

  • Enteros

Números enteros desde un valor negativo alto hasta otro valor positivo alto. El valor máximo se conoce como MAXINT y depende de la cantidad de bits utilizados en la computadora para representar un número. En la mayor parte de las computadoras actuales esta cantidad es de 32 bits, lo que implica que MAXINT se acerque a los dos billones.

También podemos utilizar enteros sin signo lo que incluye números positivos y el cero. De esta manera el número máximo alcanzable equivale a dos por MAXINT, o cuatro billones en una computadora de 32 bits.

Dado que el tamaño de los enteros está restringido a MAXINT, cuando sumamos dos enteros cuyo total es mayor que MAXINT, el resultado obtenido es incorrecto. En algunos lenguajes y sistemas el resultado incorrecto igual se devuelve (usualmente con algún tipo de aviso secreto que uno puede revisar si cree que pudo haber habido algún problema). Normalmente en estos casos se produce un error, el cual será manejado por el programa o directamente éste finalizará. Pithon utiliza este último sistema, mientras que TCL ha adoptado el primero. BASIC produce un error pero no provee ningún método para tratarlo (al menos yo no sé cómo).

  • Números Reales

Estos son las fracciones. Pueden representar números muy altos, más altos que MAXINT pero con menor precisión. Esto quiere decir que dos números reales que deberían ser idénticos pueden no serlo cuando son examinados por la computadora. Esto se debe a que la computadora trabaja por aproximación en los más mínimos detalles. De esta forma 4,0 podría ser representado como 3,9999999…. o 4.00000001. Estas aproximaciones son lo suficientemente precisas para la mayor parte de nuestros objetivos, pero ocasionalmente pueden ser importantes para alguna tarea específica. Recuerda esto si obtienes un resultado extraño al utilizar números reales.

  • Números Complejos o Imaginarios

Si tienes una formación científica o matemática seguramente los conocerás muy bien. Si este no es tu caso, lo más probable es que ni siquiera hayas escuchado hablar de los números complejos. De todos modos, algunos lenguajes de programación -Fortran, por ejemplo- permiten trabajar con números complejos. La mayor parte del resto, como Python, proveen una librería de funciones que permiten operar con números complejos. Y antes de que preguntes, lo mismo se aplica para las matrices.

  • Valores Booleanos – Verdadero y Falso

Como indica el encabezado, este tipo presenta sólo dos valores: verdadero o falso. Algunos lenguajes manipulan los valores booleanos directamente, mientras que otros usan una convención por medio de la cual un valor numérico (en general 0) representa 'falso' y otro (1 o -1) equivale a 'verdadero'.

En general se conoce a los valores booleanos como "valores de verdad" debido a que son utilizados para comprobar si algo es verdadero o falso. Por ejemplo, si escribimos un programa que realice backups de todos los archivos en un directorio, lo que debemos hacer es copiar un archivo y luego preguntarle al sistema operativo por el nombre del siguiente archivo. Si no hay más archivos responderá con una cadena vacía, entonces podremos comprobar que la cadena está vacía y guardar el resultado como un valor booleano (verdadero si está vacía). Pronto verás cómo utilizaremos este resultado más adelante en este curso.

  • Colecciones

Las ciencias de la Computación han creado una disciplina en sí misma para estudiar las colecciones y sus diversos comportamientos. Algunos de los nombres que podrás encontrar son:

Matrices o vectores

Una lista de ítems que pueden ser indexados para una recuperación sencilla y rápida. Usualmente es necesario aclarar de entrada cuántos ítems deseamos guardar en la matriz. Por ejemplo, si tenemos un vector llamado A, podemos recuperar su tercer ítem escribiendo A[3]. (En realidad, generalmente los vectores comienzan en la posición 0, por lo cual deberíamos escribir A[2]). Las matrices o vectores son fundamentales en BASIC ya que son la única colección predeterminada. En Python las matrices se simulan por medio de listas (ver más abajo) y Tcl implementa las matrices como diccionarios (ver más abajo).

Listas

Una lista es una secuencia de ítems. La diferencia con los vectores es que una lista puede seguir creciendo al agregársele un nuevo ítem. En general no están indexadas, por lo cual se hace necesario buscar el ítem requerido recorriendo desde el principio al fin toda la lista y evaluando cada elemento para ver si es el que nosotros buscamos. Tanto Python como Tcl trabajan con listas, mientras que en BASIC debemos utilizar algunos trucos para simularlas. Los programadores de BASIC en general crean matrices muy grandes para superar esta debilidad. Python permite indexar las listas -en realidad estrictamente no maneja vectores, pero combina en un mismo elemento la posibilidad de indexación de los vectores con la habilidad para crecer de las listas. Como veremos pronto esta característica es realmente muy útil.

Pilas

Piensa en una pila de bandejas en un restaurant: un asistente coloca una pila de bandejas limpias sobre las que ya había antes, y estas son tomadas una por una por los clientes. De esta manera, las bandejas que quedan abajo de todo son las menos utilizadas (y a veces no les llega nunca la oportunidad de ser usadas). Las pilas de datos funcionan del mismo modo: se agrega un dato a la pila o se retira uno de ella, pero el dato retirado es siempre el último que se colocó en la pila. Esta propiedad de las pilas se denomina con frecuencia First In Last Out ("el primero es el último") o FILO. Una característica útil de las pilas es que se puede revertir una lista de ítems al colocarla en la pila y luego retirarla. El resultado será una lista inversa respecto de la original. Las pilas no son tipos predeterminados en Python, Tcl o Basic, por lo que es necesario crear una función para implementarlas. Sin embargo, las listas son en general el mejor punto de partida, ya que pueden crecer -igual que las pilas- según sea necesario.

Bolsas

Una bolsa es una colección de ítems sin un orden específico y que puede contener duplicados. Las bolsas tienen en general operadores que nos permiten agregar, buscar y borrar los ítems. En Python y en Tcl las bolsas son simplemente listas. En BASIC es necesario crear la bolsa a partir de una matriz grande.

Conjunto

Un conjunto tiene la propiedad de guardar únicamente un miembro de cada ítem. En estos, se puede comprobar si un ítem pertenece al conjunto (pertenencia), y agregar, remover u obtener ítems o unir dos conjuntos según la teoría matemática de los conjuntos (unión, intersección, etc.). Ninguno de los lenguajes estudiados aquí implementa conjuntos directamente, pero pueden ser utilizados en Python y en Tcl gracias al tipo de datos llamado diccionario.

Cola

Una cola es similar a una pila excepto que el primer elemento de una cola es el primero en ser retirado. Esto se conoce como First In First Out ("el primero es el primero") o FIFO.

Diccionarios

Un diccionario combina las propiedades de las listas, los conjuntos y los vectores. Es posible seguir agregando elementos (como en las listas) y también acceder a los ítems mediante una clave provista en el punto de inserción (como con los vectores). Debido a que el acceso a los datos se realiza por medio de una clave, ésta debe ser necesariamente única ya que si no se perdería la referencia (como en los conjuntos). Los diccionarios son estructuras inmensamente útiles y son tipos de datos predeterminados tanto en Python como en Tcl. En BASIC son muy poco utilizados dadas la dificultad de implementarlos de manera eficiente.

Podemos utilizar los diccionarios de muchas maneras y más adelante veremos varios ejemplos. Por ahora, veamos cómo crear un diccionario en Python, cómo agregar algunos datos y cómo recobrarlos:

>>> dict = {}

>>> dict['booleano'] = "Un dato cuyo valor puede ser verdadero o falso"

>>> dict['entero'] = "Un número entero"

>>> print dict['booleano']

Sencillo ¿no?

Hay muchos otros más pero estos son los principales con los que trabajaremos en este curso (de hecho, sólo utilizaremos algunos de estos).

  • Archivos

Como usuario de computadoras sabrás todo acerca de los archivos, la base de prácticamente todo lo que hacemos con una computadora. No es sorprendente entonces que la mayor parte de los lenguajes de programación incluyan un tipo especial de datos llamado archivo. Dado que los archivos y su procesamiento son tan importantes, dejaré para más adelante su discusión en una sección especial.

  • Fecha y Hora

La fecha y la hora a veces se incluyen como predeterminados en algunos lenguajes. En otros casos son representados simplemente por un número alto (típicamente el número de segundos a partir de una determinada fecha u hora). Para otros lenguajes este tipo se representa de manera compleja, como veremos en la próxima sección. Este procedimiento vuelve más sencillo recuperar el mes, el día o la hora.

  • Tipos Complejos/Definidos por el Usuario

Muchas veces los tipos básicos descriptos más arriba no son adecuados para determinada tarea aunque los combinemos por medio de colecciones. En ocasiones deseamos agrupar varios datos juntos y tratarlos como si fueran un solo elemento. Un ejemplo de esta situación podría ser la descripción de una dirección postal: la calle, el número, la ciudad y el código postal. La mayor parte de los lenguajes nos permiten agrupar estos datos en un registro o estructura.

En BASIC un registro de este tipo se realiza así:

Type Dirección

Numero_Casa AS INTEGER

Calle AS STRING

Ciudad AS STRING

Cod_Postal AS STRING

End Type

En Python es algo diferente:

class Dirección:

def __init__(self, Casa, Calle, Ciudad, Codigo):

self.Numero_Casa = Casa

self.Calle = Calle

self.Ciudad = Ciudad

self.Cod_Postal = Codigo

Puede parecerte medio esotérico, pero no te preocupes que pronto tendrá su sentido.

Veremos cómo utilizar estas estructuras en nuestra próxima sección dedicada a las variables.

  • Variables

Los datos son almacenados en la memoria de la computadora. Podemos comparar este proceso con las casillas de correo donde se colocan las cartas. Uno podría colocar una carta en cualquier casilla, pero si estas no tienen una etiqueta que las identifique, resultará prácticamente imposible recuperar la carta. Para seguir con la comparación, las variables son las etiquetas de las casillas en la memoria de la computadora.

Ahora ya conocemos qué son los datos, pero ¿qué podemos hacer con ellos? Desde el punto de vista de la programación podemos crear instancias de los datos (organizados en objetos) y asignarlas a variables. Una variable es una referencia a un área específica de la memoria de la computadora donde se guardan los datos. En algunos lenguajes de programación la variable debe coincidir con el tipo de dato al cual apunta. En BASIC, por ejemplo, declaramos una variable de cadena agregándole el signo $ al final del nombre:

DIM MICADENA$

MICADENA$ = "Esta es una cadena"

En este ejemplo DIM MICADENA$ crea la referencia y especifica el espacio para almacenar la cadena (y sabemos que se trata de una cadena por el signo $). La línea MICADENA$ = "Esta es…" define los datos y los coloca en el espacio de la memoria denominado MICADENA$.

De forma similar declaramos un entero mediante el signo % al final del nombre:

DIM MIENTERO%

MIENTERO% = 7

En Python y en Tcl una variable adquiere el tipo de datos que se le asigna por primera vez y lo mantendrá durante el programa, avisándonos si intentamos mezclar los datos de manera extraña, tal como sumar una cadena a un número (¿Recuerda el ejemplo que vimos del mensaje de error?). Es posible cambiar el tipo de datos de una variable en Python reasignando la variable.

>>> q = 7

>>> print 2*q

14

>>> q = "Siete"

>>> print 2*q

SieteSiete

Notemos que inicialmente la variable q apuntaba al número 7 y mantuvo este valor hasta que reasignamos la variable con el valor "Siete". De esta manera las variables en Python mantienen cualquier tipo de datos siendo posible modificar la referencia hacia otro tipo de datos simplemente reasignando la variable. Cuando se produce la reasignación el dato original se pierde y Python lo borrará de la memoria (salvo que se lo recupere en otra variable). A esto se denomina "recolección de basura". (Esto puede compararse con el empleado del correo que cada tanto revisa las casillas y retira aquellos paquetes que carecen de información de destino. Si nadie los reclama ni es posible encontrar a su dueño, los paquetes son incinerados.)

BASIC no permite realizar esto. Si una variable es una variable de cadena (terminada en $) jamás podremos asignar a ella un número. De forma similar, es imposible asignar una cadena a una variable de enteros (terminada en %). Por otra parte, BASIC permite 'variables anónimas' (no tienen ningún identificador después del nombre). Sin embargo, este tipo de variables sólo puede contener números enteros o reales.

Un ejemplo final con variables enteras en BASIC:

i% = 7

PRINT 2 * i%

i% = 4.5

PRINT 2 * i%

Notemos que la asignación de 4.5 en la variable i% parece funcionar, sólo que en realidad lo único que fue asignado fue la parte entera del valor. Esto nos recuerda la forma en que Python trata la división de enteros. Todos los lenguajes de programación tienen sus pequeñas idiosincracias como esta.

  1. Acceso a Tipos Complejos

Podemos también asignar un tipo de datos complejo a una variable, pero para acceder a cada uno de los campos individuales del tipo de datos deberemos utilizar algún mecanismo de acceso, que será definido por el propio lenguaje. Usualmente se trata de un punto.

Considerando el caso del tipo "dirección" que hemos definido antes, en BASIC realizaríamos lo siguiente:

DIM Direc AS Dirección

Direc.Numero = 7

Direc.Calle = "Los Rosales"

Direc.Ciudad = "Cualquiera"

Direc.Codigo = "123 456"

PRINT Direc.Numero," ",Direc.Calle

Y en Python:

Direc = Dirección(7,"Los Rosales","Cualquiera","123 456")

print Direc.Numero, Direc.Calle

ALCANCE DE LAS VARIABLES

El alcance, o vida, de una variable determina qué comandos de secuencia de comandos pueden tener acceso a dicha variable. Una variable declarada dentro de un procedimiento tiene alcance local; la variable se crea y se destruye cada vez que se ejecuta el procedimiento. No se puede tener acceso a ella desde fuera del procedimiento. Una variable declarada fuera de un procedimiento tiene alcance global; su valor es accesible y modificable desde cualquier comando de secuencia de comandos de una página ASP.

Nota: Al limitar el alcance de la variable a un procedimiento mejorará el rendimiento.

Si declara variables, una variable local y una variable global pueden tener el mismo nombre. La modificación del valor de una de ellas no afecta al valor de la otra. Sin embargo, si no declara las variables, podría modificar inadvertidamente el valor de una variable global. Por ejemplo, los siguientes comandos de secuencia de comandos devuelven el valor 1 incluso aunque haya dos variables llamadas Y:

<% Option Explicit Dim Y Y = 1 SetLocalVariable Response.Write Y Sub SetLocalVariable Dim Y Y = 2 End Sub %>

Por el contrario, los comandos siguientes devuelven el valor 2 porque las variables no se han declarado de forma explícita. Cuando la llamada al procedimiento asigna a Y el valor 2, el motor de secuencias de comandos da por supuesto que el procedimiento pretende modificar la variable global:

<% Option Explicit Dim Y = 1 SetLocalVariable Response.Write Y Sub SetLocalVariable Y = 2 End Sub %>

Para evitar problemas, adquiera el hábito de declarar explícitamente todas las variables. Lo cual es especialmente importante si utiliza la instrucción #include para incluir archivos en su archivo ASP. La secuencia de comandos incluida está contenida en un archivo aparte, pero se trata como si formara parte del archivo contenedor. Es muy fácil olvidarse de que hay que utilizar nombres de variables diferentes en la secuencia de comandos principal y en la secuencia de comandos incluida, a menos que declare las variables.

Asignar a las variables alcance de sesión o de aplicación

Las variables globales sólo son accesibles en un mismo archivo ASP. Para hacer que una variable sea accesible en varias páginas, asigne a la variable alcance de sesión o de aplicación. Las variables con alcance de sesión están disponibles en todas las páginas de una aplicación ASP que pida un mismo usuario. Las variables con alcance de aplicación están disponibles en todas las páginas de una aplicación ASP que pida cualquier usuario. Las variables de sesión son una buena manera de almacenar información para un único usuario, como sus preferencias o el nombre o la identificación del usuario. Las variables de aplicación son una buena manera de almacenar información para todos los usuarios de una determinada aplicación, como los saludos específicos o los valores generales necesarios en la aplicación.

ASP proporciona dos objetos integrados en los que puede almacenar variables: el objeto Session y el objeto Application.

También puede crear instancias de objetos con alcance de sesión o de aplicación. Para obtener más información, consulte Establecer el alcance de los objetos.

Tiempo de vida de una variable:

El tiempo de vida se refiere al intervalo de tiempo que trascurre desde que se crea la variable hasta que se destruye. En Java, una variable se crea en el momento que se ejecuta su declaración y se destruye cuando finaliza el bloque de instrucciones en donde fue declarada. Por ejemplo:

if ( … ) {

println( … );

int n= 123; // (A)

n= …; // (B)

} // (C)

La variable n se crea en el momento de ejecutar la instrucción (A) y se destruye al encontrar el final del bloque de instrucciones en (C). En (B) se asigna un nuevo valor a la variable n, que ya existía.

Un mismo identificador se puede usar para nombrar varias variables distintas. Por ejemplo en el siguiente código:

{

int x= a+b;

… x …

}

{

int x= fun(a,b);

… x …

}

En el código anterior, la variable x del primer bloque no tiene ninguna relación con la variable x del segundo bloque. Se trata de dos variables que se identifican por el mismo nombre. Se podría renombrar la variable x del segundo bloque por y, sin cambiar en nada la ejecución del programa.

Incluso, la declaración de una variable puede aparecer una sola vez en el programa, pero aún así servir para identificar varias variables durante la ejecución del programa. Esto se aprecia en el siguiente ciclo:

while ( … ) {

double x= 3.14; // (A)

x= …;

} // (C)

En cada iteración, en (A) se crea una nueva variable x que se destruye en (C), pero todas se llaman con el mismo nombre (x).

Los parámetros de una función también son variables. Se crean en el momento que la función es invocada y se destruyen al finalizar la ejecución de la función. Por ejemplo, la siguiente función calcula el máximo común divisor:

int mcd(int x, int y) {

while (x!=y) {

if (x>y)

x= x-y;

else

y= y-x;

}

return x;

}

Considere las siguientes invocaciones de esta función (por ejemplo en el procedimiento run):

int a= 15;

println( mcd(a, 21) );

println( mcd(25, a) );

En cada invocación de mcd se crean nuevas variables x e y (sus parámetros) que se inicializan con los valores de los argumentos. Esto se visualiza mejor en la siguiente figura:

Observe en la figura que durante la ejecución del programa se crean dos variables con nombre x. Esto no constituye ninguna confusión en el momento de accesar la variable x, puesto que ambas variables tienen tiempo de vista disjuntos.

Observe también, que la función m.c.d. modifica los parámetros x e y sin modificar el valor de la variable a en ninguna de las invocaciones. Una función no puede producir efectos laterales sobre las variables que se especifiquen como argumentos durante la invocación. Sin embargo, una función sí puede producir efectos laterales sobre los objetos que se pasen como argumentos en la invocación (como por ejemplo un arreglo).

Existen casos que sí existen varias variables con el mismo nombre en un mismo instante. Por ejemplo, consideremos el siguiente programa:

int x= 105;

println( mcd(210, x) );

Mientras se ejecuta la función mcd existen dos variables x. ¿Qué variable se modifica al ejecutar x= x-y;? Intuitivamente la respuesta es “la variable de mcd''. Esto es cierto, pero para aclarar mejor este concepto definiremos el alcance de una variable.

Alcance de una variable:

El alcance de una variable es el conjunto de instrucciones en la que esa variable es visible por medio de su identificador. En Java, el alcance de una variable está delimitado por la primera instrucción que sigue a su declaración en el programa, hasta el final del bloque en donde fue declarada. Por ejemplo en:

void run() {

int x= 105; // (A)

println( mcd(210, x) );

} // (B)

La variable x es conocida desde (A) hasta (B). En particular esa variable no es conocida en la definición de m.c.d., ni en la definición de ningún otro procedimiento porque esas definiciones no se encuentran entre (A) y (B).

Es importante entender la diferencia entre alcance y tiempo de vida de una variable. En el ejemplo anterior, mientras se ejecuta la función m.c.d., la variable x del procedimiento run continúa existiendo, a pesar de que no es visible desde mcd. Si una variable no es visible, no necesariamente ha sido destruida. En cambio, si una variable fue destruida, esa variable no es visible.

4. Evaluación de expresiones

En este capitulo se considerará como evaluar una expresión que contiene varias operaciones, la forma intuitiva de evaluar una expresión es evaluar una operación a la vez en un orden apropiado, el resultado de cada operación se materializa en una relación temporal para su inmediata utilización. El inconveniente de esta aproximación es la creación de relaciones temporales que implican la escritura y lectura de disco. Una aproximación alternativa es evaluar operaciones de manera simultánea en un cauce, con los resultados de una operación pasados a la siguiente sin la necesidad de almacenarlos en relaciones temporales.

  • Materialización.

Este enfoque de implementación toma la expresión y la representa en una estructura anexa (comúnmente un árbol de operadores). Luego se comienza por las operaciones de más bajo nivel, las entradas a estas operaciones son las relaciones de la base de datos, estas operaciones se ejecutan utilizando los algoritmos ya estudiados y almacenando sus resultados en relaciones temporales. Luego se utilizan estas relaciones temporales para ejecutar las operaciones del siguiente nivel en el árbol.

Una evaluación como la descrita se llama evaluación materializada, puesto que los resultados de cada operación intermedia se crean (materializan) con el fin de ser utilizados en la evaluación de las operaciones del siguiente nivel.

El costo de una evaluación materializada no es simplemente la suma de los costos de las operaciones involucradas. Dado que los costos estimados de los algoritmos presentados anteriormente no consideran el resultado de la operación en disco, por lo tanto, al costo de las operaciones involucradas hay que añadir el costo de escribir los resultados intermedios en disco. Suponiendo que los registros del resultado se almacenan en una memoria intermedia y que cuando esta se llena, los registros se escriben en el disco. El costo de copiar los resultados se puede estimar en donde es el número aproximado de tuplas de la relación resultado y es el factor de bloqueo de la relación resultado.

  • Encauzamiento.

Se puede mejorar la evaluación de una consulta mediante la reducción del número de archivos temporales que se producen. Por ejemplo, considérese el join de dos relaciones seguida de una proyección. Si se aplicara materialización en la evaluación de esta expresión implicaría la creación de una relación temporal para guardar el resultado del join y la posterior lectura de esta para realizar la proyección. Estas operaciones se pueden combinar como sigue. Cuando la operación de join genera una tupla del resultado, esta se pasa inmediatamente al operador de proyección para su procesamiento. Mediante la combinación del join y de la proyección, se evita la creación de resultados intermedios, creando en su lugar el resultado final directamente.

La implementación del encauzamiento se puede realizar de dos formas:

  1. Bajo demanda (enfoque top-down)
  2. Desde los procedimientos (enfoque bottom-up)

En un encauzamiento bajo demanda el sistema reitera peticiones de tuplas desde la operación de la cima del encauzamiento. Cada vez que un operador recibe una petición de tuplas calcula la siguiente tupla a devolver y la envía al procesador de consultas. En un encauzamiento desde los procedimientos, los operadores no esperan a que se produzcan peticiones para producir las tuplas, en su lugar generan las tuplas impacientemente. Cada operación del fondo del encauzamiento genera continuamente tuplas de salida y las coloca en las memorias intermedias de salida hasta que se llenan. Así, cuando un operador en cualquier nivel del encauzamiento obtiene sus tuplas de entrada de un nivel inferior del encauzamiento, produce las tuplas de salida hasta llenar su memoria intermedia de salida. El sistema necesita cambiar de una operación a otra solamente cuando se llena una memoria intermedia de salida o cuando una memoria intermedia de entrada está vacía y se necesitan más tuplas de entrada para generar las tuplas de salida. Las operaciones de encauzamiento se pueden ejecutar concurrentemente en distintos procesadores.

El encauzamiento bajo demanda se utiliza comúnmente más que el encauzamiento desde los procedimientos dada su facilidad de implementación.

  1. Algoritmos de encauzamiento.

Supóngase un join cuya entrada del lado izquierdo esta encauzada, dado que esta entrada no está completamente disponible, implica la imposibilidad e utilizar un join por mezcla (dado que no se sabe si la esta entrada viene o no ordenada). El ordenar la relación significa transformar el procedimiento en materialización. Este ejemplo ilustra que la elección respecto al algoritmo a utilizar para una operación y las elecciones respecto del encauzamiento son dependientes una de la otra. El uso eficiente del encauzamiento necesita la utilización de algoritmos de evaluación que puedan generar tuplas de salida según se están recibiendo tuplas por la entrada de la operación. Se pueden distinguir dos casos:

  1. Solamente una de las entradas está encauzada.
  2. Las dos entradas de un join están encauzadas.

Si únicamente una de las entradas está encauzada, un join en bucle anidado indexado es la elección más natural, ahora bien, si se sabe de antemano que las tuplas de la entrada encauzada están ordenadas por los atributos de join y la condición de join es un equi-join también se puede usar un join por mezcla. Se puede utilizar un join por asociación híbrida con la entrada encauzada como la relación para probar (relación r). Sin embargo, las tuplas que no están en la primera partición se enviarán a la salida solamente después de que la relación de entrada encauzada se reciba por completo. Un join por asociación híbrida es útil si la entrada no encauzada cabe completamente en memoria, o si al menos la mayoría de las entradas caben en memoria.

Si ambas entradas están encauzadas, la elección de los algoritmos de join se limita. Si ambas entradas están ordenadas por el atributo de join y la condición de join es un equi-join entonces se puede utilizar el método de join por mezcla. Otra técnica alternativa es el join por encauzamiento que se presenta a continuación. El algoritmo supone que las tuplas de entrada de ambas relaciones r y s están encauzadas. Las tuplas disponibles de ambas relaciones se dejan listas para su procesamiento en una estructura de cola simple. Asimismo se generan marcas especiales llamadas y , que sirven como marcas de fin de archivo y que se insertan en la cola después de que se hayan generado todas las tuplas de r y de s (respectivamente). Para una evaluación eficaz, se deberían construir los índices apropiados en las relaciones r y s. Según se añaden las tuplas a ambas relaciones se deben mantener los índices actualizados.

El algoritmo de join encauzado es el siguiente:

hechor = falso; hechos = falso; r = ; s = ; resultado = ; mientras not hechor or not hechos si la cola está vacía entonces esperar hasta que la cola no este vacía;

t = primera entrada de la cola;

si t = Finr entonces hechor = verdadero; sino si t = Fins entonces hechos = verdadero; sino si t es de la entrada r entonces r = r {t}; resultado = resultado ({t} s); sino s = s {t}; resultado = resultado (r {t}); fin si fin si fin si fin mientras

  1. Hasta ahora se han estudiado algoritmos para evaluar extensiones de operaciones del álgebra relacional y se han estimado sus costos. Dado que una consulta se puede evaluar de distintas maneras y por lo tanto con distintos costos estimados, este apartado considerará formas alternativas y equivalentes a sus expresiones.

    1. Cada implementación de base de datos tiene su forma de representación interna de consultas independientes del lenguaje de consultas utilizado. La representación interna debe cumplir con la característica de ser relacionalmente completo, es por eso que comúnmente los motores de BD eligen la representación del álgebra relacional en forma de árbol sintáctico abstracto para su representación interna. Dada una expresión del álgebra relacional, es trabajo del optimizador alcanzar un plan de evaluación que calcule el mismo resultado que la expresión dada pero de la manera menos costosa de generar. Para encontrar este plan de evaluación el optimizador necesita generar planes alternativos que produzcan el mismo resultado que la expresión dada y elegir el más económico de ellos. Para implementar este paso el optimizador debe generar expresiones que sean equivalentes a la expresión dada por medio del uso de las reglas de equivalencia que se explican a continuación.

    2. Equivalencia de expresiones.
    3. Reglas de equivalencia.
  2. Transformación de expresiones relacionales.

Una regla de equivalencia dice que las expresiones de dos formas son equivalentes, por lo tanto se puede transformar una en la otra mientras se preserva la equivalencia. Se entiende como preservar la equivalencia al hecho de que las relaciones generadas por ambas expresiones tienen el mismo conjunto de atributos y contienen el mismo conjunto de tuplas.

Formalmente se dice que se representa una expresión en su forma canónica.

La noción de forma canónica es central a muchos brazos de la matemática y otras disciplinas relacionadas. Esta puede ser definida como sigue:

Dado un conjunto de Q objetos (digamos consultas) y una noción de equivalencias entre objetos (digamos, la noción de que q1 y q2 son equivalentes si y sólo si ellas producen el mismo resultado), un subconjunto C de Q se dice la forma canónica de Q (bajo la definición de equivalencia expuesta anteriormente) si y sólo si cada objeto q en Q es equivalente a sólo un objeto c en C. El objeto c es llamado la forma canónica de el objeto q. Todas las propiedades de interés que se aplican al objeto q también se aplican a su forma canónica c; por lo tanto es suficiente estudiar sólo el pequeño conjunto de formas canónicas C y no el conjunto Q con el fin de probar una variedad de resultados. Las reglas de equivalencia para llevar la expresión relacional a una equivalente son:

 

  1. Cascada de proyecciones:

  1. Cascada de selecciones:

  1. Conmutación de selecciones:

  1. Conmutación de selección y proyección.

  1. Conmutación del Join.

  1. Asociatividad del Join Natural. caso1.

caso 2. Involucra sólo atributos de y .

  1. Distributividad de la selección con respecto al join.

Caso 1. Involucra sólo atributos de .

Caso 2. involucra sólo atributos de y involucra sólo atributos de

 

  1. Distributividad de la proyección con respecto al join.

Si y son los atributos de y respectivamente.

 

  1. Conmutatividad de la unión y la intersección.

La diferencia de conjuntos no es conmutativa.

 

  1. Asociatividad de la unión e intersección.

 

  1. Distributividad de la selección con respecto a la unión, intersección y diferencia.

 

  1. Distributividad de la proyección con respecto a la unión.

 

Ejemplo: Supóngase que lo que se desea es notificar a todos los dueños de vehículos del año, que estén siendo conducidos por los chóferes con menos experiencia (supóngase un año o menos); la expresión relacional sería:

Se puede utilizar la regla 6.1 con el fin de asociar el Join.

Aplicando la regla 5 se puede conmutar el Join.

Luego se aplica la regla 7.1 con el fin de distribuir la selección sobre el join.

Aplicando la regla 7.2 se obtiene:

Las siguientes figuras muestran la expresión inicial y la final en una estructura de árbol sintáctico.

Así, los optimizadores generan de manera sistemática expresiones equivalentes a la consulta dada. El proceso se entiende como sigue: dada una expresión, se analiza cada subexpresión para saber si se puede aplicar una regla de equivalencia. De ser así se genera una nueva expresión donde la subexpresión que concuerda con una regla de equivalencia se reemplaza por su equivalente. Este proceso continúa hasta que no se pueden generar más expresiones nuevas. Una optimización en términos de espacio se puede lograr como sigue. Si se genera una expresión E1 de una expresión E2 mediante una regla de equivalencia, entonces E1 y E2 son equivalentes en su estructura y por lo tanto sus subexpresiones son idénticas. Las técnicas de representación de expresiones que permiten a ambas expresiones apuntar a la subexpresión compartida pueden reducir el espacio de búsqueda significativamente.

5.- Estructuras de control

  1. Estructuras de control

En lenguajes de programación, las estructuras de control permiten modificar el flujo de ejecución de las instrucciones de un programa.

Con las estructuras de control se puede:

  • De acuerdo a una condición, ejecutar un grupo u otro de sentencias (If-Then-Else y Select-Case)
  • Ejecutar un grupo de sentencias mientras exista una condición (Do-While)
  • Ejecutar un grupo de sentencias hasta que exista una condición (Do-Until)
  • Ejecutar un grupo de sentencias un número determinado de veces (For-Next)

Todas las estructuras de control tienen un único punto de entrada y un único punto de salida. Según fuchi, las estructuras de control se puede clasificar en: secuenciales, iterativas y de control avanzadas, by fuchi ing, de sistemas 6 sem Esto es una de las cosas que permite que la programación se rija por los principios de la programación estructurada.

Los lenguajes de programación modernos tienen estructuras de control similares. Básicamente lo que varía entre las estructuras de control de los diferentes lenguajes es su sintaxis, cada lenguaje tiene una sintaxis propia para expresar la estructura.

Otros lenguajes ofrecen estructuras diferentes, como por ejemplo los comandos guardados.

  1. Algunas estructuras de control en el lenguaje BASIC y Visual Basic.

    1. If-Then-Else
    2. Si la condición es verdadera, se ejecuta el bloque de sentencias 1, de lo contrario, se ejecuta el bloque de sentencias 2.

      IF (Condición) THEN

      (Bloque de sentencias 1)

      ELSE

      (Bloque se sentencias 2)

      END IF

    3. Select-Case
  2. Tipos de estructura de control
  • Se evalúa la expresión, dando como resultado un número.
  • Luego, se recorren los "Case" dentro de la estructura buscando que el número coincida con uno de los valores.
  • Es necesario que coincidan todos sus valores.
  • Cuando se encuentra la primera coincidencia, se ejecuta el bloque de sentencias correspondiente y se sale de la estructura Select-Case.
  • Si no se encuentra ninguna coincidencia con ningún valor, se ejecuta el bloque de sentencias de la sección "Case Else".

Select (Expresión)

Case Valor1

(Bloque de sentencias 1)

Case Valor2

(Bloque de sentencias 2)

Case Valor n

(Bloque de sentencias 3)

Case Else

(Bloque de sentencias "Else")

End Select

  1. Mientras la condición sea verdadera, se ejecutarán las sentencias del bloque.

    Do While (Condición)

    (Bloque de sentencias)

    Loop

  2. Do-While

    Se ejecuta el bloque de sentencias, hasta que la condición sea verdadera

    Do

    (Bloque de sentencias)

    Loop Until (Condición)

  3. Do-Until
  4. For-Next
  • Primero, se evalúan las expresiones 1 y 2, dando como resultado dos números.
  • La variable del bucle recorrerá los valores desde el número dado por la expresión 1 hasta el número dado por la expresión 2.
  • El bloque de sentencias se ejecutará en cada uno de los valores que tome la variable del bucle.

For (Variable) = (Expresión1) To (Expresión2)

(Bloque de sentencias)

Next

  1. Las estructuras de control básicas pueden anidarse, es decir pueden ponerse una dentro de otra.

    1. IF A > B THEN

      For X = 1 To 5

      (Bloque de sentencias 1)

      Next

      Else

      (Bloque de instrucciones 2)

      End If

    2. Estructura For-Next dentro de una estructura If-Then-Else
    3. Estructura If-Then-Else dentro de estructura For-Next

      If A = C Then

      GHDGDVDVVDWVD

      Else

      EEFFSGWSGGSW

      SNSBS

      End If

      Next

    4. For SKWSx = 10 To 20
    5. Estructura For-Next que está dentro de estructura Do-While
  2. Estructuras anidadas

Do While A > 0

For X = 1 to 10

(Bloque de instrucciones)

Next

A = A – 1

Loop

contador=0 for(i=1;i<=10;i++) do {

adrian=adrian+contador

}

Estructura If-Then dentro de estructura For-Next dentro de estructura Do-While ===

Do While A > 0

For X = 1 to 10

If A = C Then

(Bloque de instrucciones1)

Else

(Bloque de instrucciones2)

End If

Next

Partes: 1, 2, 3
 Página anterior Volver al principio del trabajoPágina siguiente