Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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
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:
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.
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.
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
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:
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.
En JavaScript, los arreglos se crean con el constructor Array() y el operador new. Las
siguientes declaraciones son validas:
b. var W = [[1,2],[3,4],[5,6],[7,8]];
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>
Arreglos Bidimensionales
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:
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.
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.
Donde:
Ui: Límite superior de la dimensión
Li: Límite inferior de la dimensión
Luego,
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
OV = DB – D1 * L1 – D2 * L2 – D3 * tTipo
L-value(A[i, j, k]) = OV + D1 * i + D2 * j + k * D3
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.
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.
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.
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.