Está en la página 1de 17

FILMINAS COMENTADAS

SINTÁXIS Y SEMÁNTICA DE LENGUAJES

INTRODUCCIÓN

El presente documento, toma como hilo conductor las filminas de clase e incluye notas
y ejemplos que pretenden explicar sintéticamente los principales contenidos de la
asignatura. Es una guía, que debe ser ampliada luego con la lectura bibliográfica, el
material digital complementario propuesto, la resolución de trabajos prácticos y la
indagación personal y autónoma por parte de los alumnos.

TIPOS ESTRUCTURADOS

Def. Objeto de Datos Estructurado


Una estructura de datos es un objeto de datos que contiene otros objetos de datos como
sus elementos o componentes miembros.
Ejemplos

Algunos tipos estructurados los define el programador; otros los provee el lenguaje y
otros los define el sistema durante la ejecución del programa.
En general, dentro de los tipos estructurados tenemos:
1. Arreglos (unidimensionales/vectores, bidimensionales/matrices, n-
dimensionales).
2. Registros
3. Cadenas (en algunos lenguajes, como C/C++ son un caso particular de los
vectores)
4. Conjuntos o Enumeraciones
5. Archivos
6. TAD (Tipos Abstractos de Datos). Estos últimos son los precursores de la POO
(programación orientada a objetos) y con ellos podemos construir: listas, pilas u
otro tipo de estructuras.

En particular, para cada uno de estos tipos de estructuras de datos nos interesa analizar:

1. ¿Cómo se especifica? (Por ejemplo: cómo se especifica un arreglo o un


registro, cómo se especifica su estructura y sus componentes y cómo se
declaran objetos de datos de este tipo. También, qué operaciones son
susceptibles de aplicar sobre objetos de este tipo y cuáles sobre sus
componentes.

2. ¿Cómo se implementa? Esto tiene que ver con la asignación y gestión del
almacenamiento para cada tipo de estructura; y con las formas de referenciar a
las estructuras almacenadas y a sus componentes o miembros.

3. ¿Cómo se realiza la verificación de tipos? Sobre tipos de datos estructurados.


Veamos algunos conceptos generales en este sentido para luego abordar cada tipo de
estructura en particular:

Una estructura es de tamaño fijo si el número de componentes


es invariable durante su tiempo de vida (ejemplo: arreglos o
registros).
TAMAÑO

Una estructura es de tamaño variable (o dinámica) si el


número de componentes cambia en forma dinámica (ejemplo:
pilas, conjuntos, archivos o listas).

Notas:
 Las cadenas pueden ser de uno u otro tipo.
 Es posible que para una estructura dinámica se pueda definir un tamaño
máximo, pero esto no implica que la estructura en sí sea de tamaño fijo.

Una estructura de datos es Homogénea si todos sus


componentes son del mismo tipo. (ejemplo: arreglos, cadenas y
enumeraciones).
TIPOS

Una estructura de datos es Heterogénea si sus componentes son


de tipos distintos. (ejemplo: registros, archivos y listas)

En cuanto al mapeo en memoria de estructuras existen dos esquemas o


representaciones de almacenamiento posibles:

Representación Secuencial: En este esquema la estructura se


guarda en un solo bloque contiguo de memoria (alternativamente
puede contener también un descriptor con los atributos básicos de
la estructura).
MAPEO

Representación Vinculada o Enlazada: En este esquema los


componentes de la estructura se guardan en bloques separados,
no contiguos de memoria, vinculados por apuntadores.
El esquema de representación en memoria de una estructura es de suma importancia
para determinar e implementar mecanismos eficientes de acceso a sus componentes o
miembros. Por ejemplo: los elementos en un vector v[5] o un campo en un registro.
Alumno.nombre
En estructuras con representación secuencial, la dirección de un componente
particular se puede calcular como:

Dirección de Base + Desplazamiento

Ejemplo
Si declaro un vector int v[7] y quiero acceder al 4 elemento.

Observar que este esquema, que como veremos más adelante es lo que subyace a la
indización, es muy eficiente porque con un solo cálculo se puede obtener la dirección
de memoria de cada elemento en la estructura.
La selección de un componente en estructuras vinculadas implica seguir una cadena
de punteros desde el primer bloque de almacenamiento hasta el bloque deseado.

Ejemplo

Esto obviamente es mucho menos eficiente en el acceso por que se deben leer todos
los punteros y no se dispone de un mecanismo de acceso por cálculo directo. Aunque
es más eficiente en el uso de la memoria.

De igual forma que en los tipos elementales de datos, los objetos de datos estructurados
tienen un scope (o ámbito), pertenecen a algún entorno de referencia y tienen un tiempo
de vida útil durante la ejecución de un programa.

Recordar:
 El tiempo de vida útil de cualquier objeto de datos comienza cuando se efectúa
el enlace del objeto a una localidad de almacenamiento particular, es decir
cuando se asigna un bloque de memoria; y concluye cuando se disuelve este
enlace, o sea se libera la memoria para otro uso.
 Al tiempo que se crea un objeto de datos, se debe crear también una o más rutas
de acceso a él a través de asociaciones a nombres o a través de apuntadores
que son reconocidos en algún ambiente de referencia.

Ejemplo

Veamos a continuación los aspectos semánticos relacionados con cada tipo


estructurado. Debemos recordar que cuando hablamos de semántica de datos, nos
interesa describir: cómo se representan esos datos en memoria y qué operaciones se
pueden aplicar sobre ellos.
Cadenas

Las cadenas o Strings son arreglos unidimensionales de caracteres. Dependiendo del


lenguaje, pueden representarse en memoria en alguno de los formatos que ilustra la
filmina.

La representación estática (no varía su tamaño) y secuencial es eficiente en el acceso,


pero tiene un tamaño fijo. Si queremos asignar una cadena de mayor longitud al espacio
reservado, no será posible. Y si se alojan cadenas de menor longitud, se estará
desperdiciando memora. El carácter \0 (delimitador de fin de cadena) marca el final de
la cadena para cuando se recupera la secuencia.
Las cadenas de longitud fija, pueden tener o no un descriptor en memoria, asociado a
la estructura de datos propiamente dicha.
Las cadenas con representación enlazada permiten un tamaño dinámico, que puede
crecer y decrecer en tiempo de ejecución del programa. Esto optimiza el uso de la
memoria y da mayor flexibilidad al programa, pero ralentiza el acceso a los datos, porque
hay que seguir la secuencia de vínculos.
Arreglos Unidimensionales

Como anteriormente hemos mostrado, los arreglos son estructuras homogéneas (ya
que todos sus componentes son del mismo tipo) y de tamaño fijo. Estas características
posibilitan que la estructura tenga una representación secuencial.
Lo anterior implica que un vector se mapea en memoria como una secuencia
consecutiva de bytes.
En el ejemplo int x[8], x es en realidad un puntero a la dirección de base del vector; y
desde allí se asigna espacio consecutivo para almacenar 8 elementos indizados de 0 a
7.
Esto último implica que los arreglos se manejan por referencia. Por esto, hacer:

Como vimos, para estructuras con representación secuencial, el acceso a los miembros
es directo a través del cómputo de la

Así, en la asignación

X[5]=15;
5745 + 5 * 2 = 5755
Observar que en realidad, el subíndice (si se sub-indiza desde 0), representa la cantidad
de elementos que dejamos atrás hasta llegar al elemento buscado. Si a este número, lo
multiplicamos por lo que pesa en memoria cada elemento (tTipo – tamaño de tipo),
obtenemos la cantidad de bytes a desplazarse para llegar al elemento buscado.

Nota: se debe tener en cuenta que C/C++ no comprueba límites. Entonces, es posible
que se direccionen elementos fuera de rango, produciendo errores de lógica o posibles
errores en tiempo de ejecución.

En nuestro ejemplo:

con lo cual se estará asignando un valor fuera de rango, o sea… basura.


Se debe observar que en realidad es más peligroso si asigno datos a este espacio, ya
que se pueden estar pisando datos asignados a otro objeto de datos.

Subdirecciones con rango


Muchos lenguajes permiten al programador definir explícitamente por código, los límites
que desea utilizar para sub-indizar arreglos.
Ejemplo
En Fortran 90 se puede declarar
Real, DIMENSION (-1:4)::x

Este enunciado declara un arreglo unidimensional de 6 elementos cuyos límites de subíndices


van de -1 a 4. Así, el espacio que ocupa es (U1-L1+1 = 4 – (-1)+1 = 6).

En estos casos, se debe desplazar la dirección de base, hacia atrás o hacia adelante
según corresponda, calculando el OV (origen virtual) para que luego resulte correcto
el cálculo del desplazamiento en función de los subíndice con rango, k * tTipo (tamaño
de tipo de datos de los elementos del arreglo).

En nuestro ejemplo
X(2)::=3; Estaríamos asignando el 5 elemento del arreglo
El OV (Origen Virtual), como su nombre lo indica es una dirección de base virtual para
el arreglo. Es decir, no es la dirección de base real que el vector tiene en memoria, pero
sirve a los efectos de que luego, el cálculo del desplazamiento esté exclusivamente en
función del subíndice.

Operaciones sobre vectores


Las operaciones que actúan sobre vectores completos, como una unidad, se
implementan fácilmente usando la representación secuencial del almacenamiento para
vectores.
 La asignación de un vector a otro con los mismos atributos se implementa
copiando el contenido del bloque de memoria asignado a uno en el bloque
asignado al otro.
 Las operaciones aritméticas como la suma o el producto interno (norma), se
implementan mediante iteraciones que procesan los elementos del vector en
serie.

Un problema con esto es la cantidad de memoria que en ocasiones se necesita para


estructuras intermedias de cálculos temporales.

Ejemplos de arreglos en JavaScript

En JavaScript, los arreglos se crean con el constructor Array() y el operador new. Las
siguientes declaraciones son validas:

a. var a = new Array() //Crea un arreglo vacío sin elementos

b. var W = [[1,2],[3,4],[5,6],[7,8]];

c. var a = new Array(5,4,3,2,1,"Arreglos, prueba") //Especifica por extensión los n


elementos de un arreglo

d. var a = new Array(10) //Crea un arreglo de 10 elementos

e. var aVocales = new Array();


aVocales[0] = 'a';
aVocales[4] = 'u';

De estas declaraciones se debe observar:

 JavaScript es un lenguaje débilmente tipado. Por ello, un elemento de un arreglo


puede ser de cualquier tipo de dato (entero, booleano, String, etc.), un mismo
arreglo puede contener diferentes elementos los cuales pueden ser de un tipo
de dato diferente. O sea, JavaScript maneja arreglos heterogéneos.
 En lenguajes como C, C++ y JAVA un arreglo posee un número fijo de elementos
el cual debe ser especificado cuando se crea el arreglo, en JavaScript el
mecanismo es diferente y un arreglo puede contener cualquier número de
elementos y cambiar el número de elementos en cualquier momento. O sea,
JavaScript maneja arreglos dinámicos, mientras que C/C++ maneja arreglos
estáticos.

La propiedad length de JavaScript, es una propiedad de los Array que da la longitud de


un arreglo en un momento dado de la ejecución de un programa.

Ejemplo

<script language="JavaScript">
var a = new Array(); alert (a.length); //muestra 0
a = new Array(10);; alert (a.length); //muestra 10
a = new Array(1,2,3); alert (a.length); //muestra 3 - subindiza de 0 a 2
a = [4,5]; alert(a.length); //muestra 2
a[5] = -1; alert(a.length); //muestra 6
</script>

Otros métodos de arreglos en JavaScript son concat(), reverse(), sort(), etc.

Arreglos Bidimensionales

Un arreglo bidimensional es una matriz compuesta de filas y columnas.


Como podemos ver en la filmina, una matriz int x[4][3] se representa a alto nivel como
una tabla de doble entrada de 4 filas por 3 columnas; y para acceder a un elemento
particular necesito un subíndice para cada dimensión.

Sin embargo, si vemos a una matriz como un vector de vectores, es fácil ver que cumple
con las propiedades de homogeneidad y tamaño fijo y por lo tanto tiene una
representación secuencial en memoria, es decir: nuestra matriz x se mapea como una
secuencia consecutiva de bytes.
Así, la referencia al elemento x[2][1], puede ser computada como:

Si la matriz se sub-indiza desde 0, entonces el subíndice i indica cuántas filas completas


deben dejarse atrás para posicionarnos al principio de la fila en donde está el elemento
buscado. Si este subíndice se multiplica por lo que pesa cada fila (D dimensión fila)
entonces tenemos la cantidad de bytes a saltar para llegar a la fila que contiene al
elemento buscado. Una vez que nos posicionamos en la fila deseada, el subíndice j nos
indica la cantidad de elementos concretos a dejar atrás hasta llegar a la ubicación del
elemento buscado. Si esta cantidad j la multiplicamos por lo que pesa cada elemento
(tTipo), obtenemos la cantidad de bytes a saltar en la fila para llegar al elemento
buscado. De esta forma, la fórmula del cálculo del desplazamiento para arreglos
bidimensionales sub-indizados desde cero está exclusivamente en función de los
subíndices del arreglo.
Al estudiar un lenguaje, es importante saber si una matriz se considera como una
“columna de filas” (esto es en C/C++ y como lo hemos hecho en nuestro ejemplo) o
como una “fila de columnas”. La estructura más común es la de columna de filas, que
se conoce en general como “Orden por filas”.

Subíndices con Rango para arreglos bidimensionales


Hasta ahora hemos considerado arreglos que se sub-indizan desde 0 a (dimensión filas
– 1) para las filas y desde 0 a (dimensión columnas – 1) para las columnas. Esto es así
en C/C++ y en JavaScript. Sin embargo, muchos lenguajes permiten definir
explícitamente límites inferior y superior para los subíndices tanto de filas como de
columnas.
En la siguiente filmina, se consideran los conceptos vistos anteriormente pero ahora
aplicados a la manipulación de arreglos bidimensionales con rango:
Ejemplo

Observar que lo que se hace en la filmina es, a partir de esta nueva definición de límites
L1, U1, L2 y U2, redefinir de manera general las fórmulas de acceso vistas
anteriormente.
El Origen Virtual en realidad lo que hace es quitarle a la DB, lo que luego se le va a
sumar (de más) debido a que tanto el subíndice i, como el subíndice j no parten desde
0.

Arreglos Estáticos vs. Dinámicos


Si recordamos, un objeto de datos declarado estáticamente era aquel para el cuál su
tipo de datos (formato de representación en memoria, dimensión, dominio y
operaciones) era conocido en tiempo de compilación. Mientras que un objeto de datos
declarado dinámicamente era aquel que se asociaba a la información de tipo
dinámicamente en tiempo de ejecución, mediante el mantenimiento de un descriptor de
tipo asociado al objeto.
Estos mismos conceptos, pueden ser aplicados al caso de los objetos de datos
estructurados, tal como lo ilustra la siguiente filmina.

Aquí se observa, para el caso de los arreglos dinámicos que el descriptor de tipo
mantiene toda la información necesaria para poder representar en memoria y manipular
adecuadamente el objeto en tiempo de ejecución.
Arreglos tridimensionales
Siguiendo el mismo criterio que para el caso de las matrices, los arreglos
tridimensionales también se mapean en memoria como secuencias de bytes
consecutivos.
La siguiente filmina, resume el esquema de mapeo en memoria y fórmulas de acceso
para arreglos tridimensionales.

Considerando un cubo sub-indizado con rango, con:


Dimensión 1 – Cantidad de planos = (U1 – L1 + 1)
Dimensión 2 – Cantidad de filas = (U2 – L2 + 1)
Dimensión 3 – Cantidad de columnas = (U3 – L3 + 1)

Donde:
Ui: Límite superior de la dimensión
Li: Límite inferior de la dimensión

Luego,

Para obtener el tamaño en memoria del arreglo deberíamos multiplicar: cantidad de


planos x cantidad de filas por plano x cantidad de columnas por filas x tamaño de cada
elemento. Esto es:
Espacio = (U3 – L3 + 1) * (U2 – L2 + 1) * (U1 – L1 + 1) * tTipo
Para obtener el L-value(A[i, j, k]) del elemento que está en el i-ésimo plano, de la j-ésima
fila, de la k-ésima columna, debemos calcular DB + desplazamiento.
Si consideramos al cubo como un vector de matrices, podemos plantear el cálculo del
desplazamiento como la suma de los siguientes términos:

1. D1 * (i-L1) : La cantidad de planos que debemos saltar para llegar al plano donde
se encuentra el elemento buscado x tamaño del plano (D1).
2. D2 * (j-L2): La cantidad de filas completas a saltar dentro del plano, para llegar
a la fila donde se encuentra el elemento buscado x tamaño de la fila (D2).
3. k * D3: La cantidad de elementos concretos que debemos saltar en la fila, para
llegar al elemento buscado x tamaño de tipo (D3).

Así,
L-value(A[i, j, k]) = DB + D1 * (i - L1) + D2 * (j - L2) + k * D3

Donde D1, D2 y D3, llamados multiplicadores, representan la cantidad de bytes que


pesa cada plano, cada fila y cada elemento respectivamente.

Luego, si aplicamos propiedad distributiva sobre la fórmula y agrupamos los términos


que no dependen de los subíndices (es decir los términos no variables), obtenemos el
Origen Virtual para los arreglos tridimensionales y el desplazamiento dependiendo
exclusivamente de los subíndices i, j y k.

OV = DB – D1 * L1 – D2 * L2 – D3 * tTipo

L-value(A[i, j, k]) = OV + D1 * i + D2 * j + k * D3

Observar, que si el arreglo se sub-indiza desde 0, entonces el OV coincide con la DB


real del arreglo.
Arreglos n-dimensionales
Generalizando, los arreglos n-dimensionales se mapean como secuencias de bytes
consecutivos en memoria. La siguiente filmina, resume el esquema de mapeo en
memoria y fórmulas de acceso para arreglos multidimensionales.

Estas fórmulas no son más que la extensión de los cálculos que hemos presentado para
el caso unidimensional, bidimensional y tridimensional. Así como una matriz puede ser
considerada un arreglo de vectores y un cubo un vector de planos o matrices, podemos
interpretar a un hipercubo como un vector de cubos y de esta forma generalizar las
fórmulas para cualquier número n de dimensiones de un arreglo.
Slices o Rebanadas
Un Slice o Rebanada es una porción de un arreglo. Es decir, un rango consecutivo de
elementos que constituye en sí mismo un arreglo.
Tal como lo muestra la filmina, lo típico es rebanar el arreglo por filas, o por columnas,
e incluso por planos.
En C/C++ las operaciones se definen sobre elementos individuales de un arreglo, no
hay operaciones sobre slices. Sin embargo, otros lenguajes como Fortran 90, ADA (en
menor medida) o APL definen un amplio rango de operaciones sobre arreglos completos
y sobre sus rebanadas (o slices), tales como comparaciones de igualdad,
comparaciones de mayor o menor, transposición, producto cartesiano, etc.
En estos lenguajes, también es posible pasar estas partes de un arreglo como
argumentos a subprogramas.

Ejemplos
En Fortran 90, para los ejemplos de la filmina haríamos.

Slice fila: A(3,*).

Slice columna: A(*,2).

Slice plano: A(3,*,*).

En JavaScript

El método slice() de un arreglo, regresa una parte o un sub-arreglo del arreglo especificado. El
primer argumento especifica el inicio y el segundo el fin del sub-arreglo que regresará. Si sólo se
especifica un argumento el arreglo que regresa contiene todos los elementos del arreglo
contenidos después de donde se indicó su inicio.

var aLetras = ['a','e','i','o','u'];var aTmp = aLetras.slice(0,3);


document.write(aTmp + '<br>'); //muestra a,e,i
aTmp = aLetras.slice(3);
document.write(aTmp + '<br>'); //muestra o,u
aTmp = aLetras.slice(1,-1);
document.write(aTmp + '<br>'); //muestra e,i,o,a

Arreglos Asociativos
Los arreglos asociativos permiten asociar nombres simbólicos a los subíndices del
arreglo, de forma que su gestión a alto nivel pueda hacerse a través de estos nombres
en lugar de utilizar subíndices numéricos.
Ejemplo
El ejemplo en JavaScript que presenta la filmina asocia un nombre de ciudad a cada elemento
del arreglo. De esta forma, el arreglo ítems es un arreglo de enteros de 9 elementos:
Así, si hacemos var x = items[catamarca], estaremos asignando en x
el valor entero 51.

En el ejemplo se itera por el arreglo, concatenando los valores enteros


en una cadena que se muestra luego en un diálogo del tipo alert.

Nota: Se recomienda probar los ejemplos dados tanto en C/C++ como en JavaScript para
comprender su funcionamiento. Es bueno utilizar, durante el aprendizaje, los IDE de
programación y sus herramientas de debug.

También podría gustarte