Tema 71. Tipos elementales de datos. Estructuras convencionales de datos. Estructuras dinámicas de datos. Ficheros. Tipos de ficheros: descripción, funcionalidad y clasificación. Organización de ficheros. Concepto y tipos: métodos de acceso en el tratamiento de un fichero.
1. INTRODUCCIÓN Y ENCUADRE
Los programas no trabajan con información abstracta de manera indiferenciada: operan sobre representaciones concretas que ocupan memoria, admiten unas operaciones y excluyen otras. Por eso, el concepto de tipo de dato es previo al de algoritmo. Un tipo determina un dominio de valores, una representación y un repertorio de operaciones válidas. Sobre esos tipos se construyen estructuras que organizan conjuntos de elementos y, cuando la información debe sobrevivir a la ejecución del proceso, se materializa en ficheros o en otros mecanismos persistentes.
El temario distingue tres planos que conviene mantener separados. El primero es el de los tipos elementales, como enteros, reales, caracteres y booleanos. El segundo es el de las estructuras de datos, que pueden tener tamaño y disposición convencional o estática —arrays, matrices y registros— o variar durante la ejecución —listas enlazadas, árboles, grafos y tablas hash—. El tercero es el de los ficheros, donde interesa tanto el contenido lógico como la organización física, la unidad de transferencia, el método de acceso y las operaciones de mantenimiento.
La elección de una estructura nunca es puramente sintáctica. Está condicionada por el patrón de uso: número de elementos, frecuencia de consultas, inserciones y borrados, necesidad de conservar el orden, consumo de memoria, localidad de referencia, concurrencia y persistencia. Una solución correcta funcionalmente puede ser inadecuada por su complejidad temporal, por desperdiciar memoria o por obligar a recorrer un fichero completo para resolver una consulta puntual.
En sistemas de información sanitarios esta materia tiene una dimensión práctica inmediata. Un listado temporal de citas puede representarse en memoria como una colección ordenada; una cola de trabajos modela peticiones pendientes; un árbol facilita búsquedas jerárquicas; una tabla hash soporta localización por clave; y un fichero secuencial o indexado puede emplearse en procesos de intercambio, carga masiva, trazabilidad o explotación. El profesional TIC debe reconocer la abstracción utilizada, sus garantías y sus límites antes de diseñar una interfaz, un proceso batch o una integración.
2. TIPOS ELEMENTALES DE DATOS Y REPRESENTACIÓN
2.1. Concepto de Tipo de Dato
Un tipo de dato define el conjunto de valores que una variable puede tomar y las operaciones que se pueden realizar sobre esos valores. Los tipos de datos proporcionan:
- Abstracción: Ocultan detalles de representación en memoria
- Seguridad: Previenen operaciones inválidas mediante verificación de tipos
- Eficiencia: Permiten al compilador/intérprete optimizar almacenamiento y operaciones
- Legibilidad: Hacen el código más comprensible declarando qué representa cada variable
2.2. Tipos Elementales Fundamentales
| Tipo | Descripción | Rango/Valores | Tamaño Típico | Operaciones |
|---|---|---|---|---|
| Entero (Integer) | Números enteros sin parte decimal | Depende del ancho y del signo; ejemplo con signo de 32 bits: −2.147.483.648 a 2.147.483.647 | Dependiente del lenguaje y la implementación; son habituales 1, 2, 4 u 8 bytes | +, -, *, /, %, comparaciones |
| Real/Flotante (Float/Double) | Números con parte decimal (coma flotante) | IEEE 754 binary32: precisión aproximada de 7 cifras; binary64: unas 15–16 cifras, además de infinitos y NaN | binary32: 4 bytes; binary64: 8 bytes | +, -, *, /, potencias, raíces, trascendentes |
| Carácter (Char) | Símbolos individuales (letras, dígitos, signos) | ASCII: 0–127. Unicode: puntos de código hasta U+10FFFF, con valores reservados y no asignados | Depende del lenguaje y la codificación; UTF-8 usa de 1 a 4 bytes por punto de código | Comparaciones, concatenación, conversiones |
| Booleano (Boolean) | Valores lógicos | true (verdadero) false (falso) |
Representación y alineación dependientes de la implementación | AND, OR, NOT, XOR, comparaciones |
Las cadenas se estudian como estructura secuencial en un apartado posterior. Algunos lenguajes las ofrecen como tipo incorporado, pero conceptualmente no son un valor elemental indivisible, sino una colección de caracteres o unidades de código con reglas propias de codificación e inmutabilidad.
2.3. Representación en Memoria
2.3.1. Enteros: Complemento a Dos
Los enteros con signo se representan típicamente usando complemento a dos, que permite aritmética eficiente:
Ejemplo 8 bits: +5: 00000101 -5: 11111011 (invertir bits de +5 y sumar 1) Ventaja: Suma/resta funcionan igual independientemente del signo +5 + (-5) = 00000101 + 11111011 = 00000000 = 0
2.3.2. Reales: Formato IEEE 754
Los números de coma flotante se representan según el estándar IEEE 754:
- 1 bit de signo: 0 = positivo, 1 = negativo
- 8 bits exponente: Con sesgo de 127
- 23 bits de fracción del significando: Parte fraccionaria del significando normalizado; en números normales existe un bit inicial implícito
Valor = (-1)^signo × 1.mantisa × 2^(exponente-127)
Ejemplo: 5.75 = 101.11₂ = 1.0111₂ × 2²
- Signo: 0
- Exponente: 2 + 127 = 129 = 10000001₂
- Mantisa: 0111 0000 0000 0000 0000 000
2.3.3. Caracteres: ASCII y Unicode
- ASCII (American Standard Code for Information Interchange): 7 bits (128 códigos). La expresión «ASCII extendido» agrupa varias páginas de códigos de 8 bits y no designa un único estándar. ‘A’ = 65, ‘a’ = 97, ‘0’ = 48
- Unicode: Estándar universal con >1 millón de caracteres
- UTF-8: Codificación variable 1-4 bytes, compatible con ASCII
- UTF-16: 2 o 4 bytes
- UTF-32: 4 bytes fijos
2.4. Representación, dominio y operaciones
Un tipo no se limita al número de bytes reservado. En sentido abstracto comprende el conjunto de valores posibles y las operaciones cerradas o definidas sobre ellos. Por ejemplo, la suma de enteros produce normalmente otro entero del mismo dominio, salvo desbordamiento; la división entera puede truncar; y la comparación booleana produce un valor lógico. Esta visión permite razonar con independencia de un lenguaje concreto.
Los tamaños de los tipos primitivos no son universales. Algunos lenguajes fijan tamaños concretos para determinados tipos, mientras que otros los vinculan a la plataforma, a la implementación o a un perfil normativo. Por ello, en material técnico resulta más correcto hablar de rangos cuando el lenguaje los garantiza y evitar presentar como universal que todo int ocupa cuatro bytes.
Los enteros sin signo representan valores no negativos. Con n bits abarcan de 0 a 2^n−1, aunque la etiqueta ^ no está permitida en la plantilla y debe interpretarse aquí como potencia. En los enteros con signo basados en complemento a dos, un bit participa en la codificación del signo y el rango es asimétrico: existe un valor negativo más que positivos distintos de cero. El desbordamiento puede producir excepción, saturación, aritmética modular o comportamiento dependiente del lenguaje.
La coma flotante IEEE 754 representa valores finitos normalizados y subnormales, ceros con signo, infinitos y valores NaN. No todos los decimales tienen representación binaria exacta; por ello, comparaciones como 0.1 + 0.2 == 0.3 pueden no comportarse como espera quien razona en decimal. Para importes monetarios o cálculos que exigen decimal exacto suelen preferirse tipos decimales, enteros escalados o bibliotecas de precisión arbitraria.
3. SISTEMAS DE TIPOS, CONVERSIONES Y SEMÁNTICA
3.1. Tipado fuerte y tipado débil
La oposición entre tipado fuerte y débil no dispone de una definición única aceptada por todos los autores. Se utiliza para describir hasta qué punto un lenguaje impide mezclar valores incompatibles, exige conversiones explícitas y evita reinterpretaciones inseguras. Por ello conviene tratarla como un espectro y explicar la regla concreta, no limitarse a una etiqueta.
| Aspecto | Mayor disciplina de tipos | Mayor coerción o permisividad |
|---|---|---|
| Operaciones incompatibles | Se rechazan salvo conversión definida. | Pueden activar conversiones implícitas. |
| Reinterpretación de memoria | Restringida por el sistema de tipos. | Puede permitirse mediante conversiones de bajo nivel. |
| Riesgo característico | Mayor necesidad de conversiones explícitas. | Resultados sorprendentes por coerción o pérdida de información. |
| Ejemplos orientativos | Python, Java y Haskell suelen describirse como fuertemente tipados. | JavaScript admite numerosas coerciones; la clasificación de C depende del criterio usado, pues combina comprobación estática con conversiones y operaciones de bajo nivel. |
3.2. Tipado estático y tipado dinámico
En el tipado estático, la compatibilidad de tipos se comprueba principalmente antes de ejecutar, aunque pueden existir comprobaciones dinámicas adicionales. Java, C#, C++, Rust y Go son ejemplos. Favorece detección temprana, refactorización y optimización, pero no garantiza por sí solo ausencia de errores.
En el tipado dinámico, los valores llevan información de tipo que se comprueba durante la ejecución. Python, JavaScript y Ruby son ejemplos. Aporta flexibilidad y metaprogramación; los errores de tipo aparecen cuando se ejecuta la ruta afectada, aunque pruebas, anotaciones y analizadores estáticos pueden detectarlos antes.
El rendimiento no se deduce únicamente del sistema de tipos: compilación JIT, representación de objetos, especialización y optimizaciones de la implementación pueden reducir o ampliar la diferencia.
3.3. Conversión, promoción y coerción
La conversión de tipos puede ser explícita, cuando el programador solicita una transformación, o implícita, cuando la realiza el lenguaje. Una promoción suele ampliar el dominio sin pérdida —por ejemplo, de un entero pequeño a otro mayor—, mientras que una conversión estrecha puede perder rango, precisión o información. Convertir un real a entero puede truncar o redondear según la operación empleada; convertir texto a número puede fallar si la cadena no cumple el formato.
La coerción es una conversión implícita aplicada para hacer compatibles operandos. Facilita la escritura, pero puede ocultar errores: concatenar accidentalmente texto y números, comparar valores de dominios diferentes o aceptar valores nulos donde no proceden. Los lenguajes y herramientas de análisis estático intentan equilibrar comodidad y seguridad.
3.4. Nulidad, opcionalidad y valores centinela
La ausencia de valor debe modelarse deliberadamente. Usar 0, −1 o una cadena vacía como centinela mezcla el dominio real con un significado especial. Los tipos opcionales, anulables o algebraicos distinguen entre “hay valor” y “no hay valor”, obligando a tratar ambos casos. Esta distinción es especialmente relevante en información clínica y administrativa, donde “desconocido”, “no aplicable”, “no informado” y “valor cero” no son equivalentes.
3.5. Tipos por valor y por referencia
En una semántica por valor, la asignación copia el contenido lógico; en una semántica por referencia, varias variables pueden referirse al mismo objeto. El aliasing permite compartir estructuras, pero introduce efectos laterales: una modificación visible a través de una referencia puede aparecer por otra. La inmutabilidad reduce este riesgo y facilita razonamiento, concurrencia y pruebas.
4. ESTRUCTURAS CONVENCIONALES: ARRAYS, MATRICES Y REGISTROS
4.1. Concepto de Estructura de Datos
Una estructura de datos es una forma particular de organizar y almacenar datos para que puedan ser accedidos y modificados eficientemente. La elección de la estructura de datos apropiada depende de:
- Operaciones requeridas: Búsqueda, inserción, eliminación, ordenación, recorrido
- Frecuencia de operaciones: Qué operaciones son más comunes
- Restricciones de espacio: Memoria disponible
- Restricciones de tiempo: Requisitos de rendimiento
- Complejidad de implementación: Simplicidad vs. sofisticación
4.2. Arrays (Arreglos/Vectores)
- Definición: Colección de elementos del mismo tipo almacenados contiguamente en memoria
- Acceso: Directo mediante índice en tiempo O(1)
- Tamaño: El array nativo suele tener longitud fija; colecciones como Python list o Java ArrayList implementan arrays dinámicos redimensionables
- Dimensiones: Unidimensionales (vectores), multidimensionales (matrices, tensores)
- Memoria: Contigua, predecible, cache-friendly
4.2.1. Operaciones en Arrays
| Operación | Complejidad | Descripción |
|---|---|---|
| Acceso por índice | O(1) | array[i] accede directamente al elemento i |
| Búsqueda (no ordenado) | O(n) | Recorrido secuencial hasta encontrar elemento |
| Búsqueda (ordenado) | O(log n) | Búsqueda binaria |
| Inserción al final | O(1) | Si hay espacio disponible |
| Inserción en posición | O(n) | Requiere desplazar elementos |
| Eliminación | O(n) | Requiere desplazar elementos para llenar hueco |
4.2.2. Arrays Multidimensionales
Los arrays multidimensionales se pueden almacenar en memoria de dos formas:
- Row-major order (fila principal): Elementos de una fila están contiguos. Usado en C, C++, Python
Array 2D: [1 2 3] Memoria: [1 2 3 4 5 6 7 8 9] [4 5 6] [7 8 9] - Column-major order (columna principal): Elementos de una columna están contiguos. Usado en Fortran, MATLAB
Array 2D: [1 2 3] Memoria: [1 4 7 2 5 8 3 6 9] [4 5 6] [7 8 9]
4.3. Registros (Records/Structs)
Un registro es una estructura de datos heterogénea que agrupa elementos de diferentes tipos bajo un único nombre.
// Ejemplo en C
struct Empleado {
int id;
char nombre[50];
float salario;
struct Fecha fechaContratacion;
};
// Ejemplo en Python (usando dataclass)
from dataclasses import dataclass
@dataclass
class Empleado:
id: int
nombre: str
salario: float
fecha_contratacion: datetime
4.3.1. Características de Registros
- Heterogeneidad: Campos pueden ser de diferentes tipos
- Acceso por nombre: empleado.nombre, empleado.salario
- Memoria: Campos almacenados secuencialmente (con posible padding para alineación)
- Anidación: Registros pueden contener otros registros
- Uso: Modelar entidades del mundo real con múltiples atributos
4.4. Localidad de referencia y coste real del array
El acceso indexado de un array es O(1) porque la dirección del elemento se calcula a partir de la dirección base, el índice y el tamaño del elemento. Esta complejidad asintótica no describe todo el rendimiento: la contigüidad mejora la localidad espacial y permite aprovechar líneas de caché, prelectura y operaciones vectorizadas. Por ello, recorrer un array puede ser más rápido que recorrer una lista enlazada incluso cuando ambas operaciones son lineales.
La inserción en una posición intermedia exige desplazar elementos y suele ser O(n). Los arrays dinámicos reservan capacidad superior al tamaño lógico y crecen por bloques; así obtienen inserción al final de coste amortizado O(1), aunque una ampliación concreta requiere reservar otra zona y copiar elementos.
4.5. Alineación y relleno en registros
Los campos de un registro pueden incorporar bytes de relleno para satisfacer requisitos de alineación. Por eso, el tamaño de una estructura no siempre es la suma exacta de sus campos. Serializar directamente la imagen de memoria de un registro puede fallar entre compiladores, arquitecturas o versiones por diferencias de alineación, endianness y representación. Un formato persistente debe definir explícitamente tamaños, orden de bytes, codificación y versión.
5. CADENAS, ENUMERACIONES Y OTRAS COLECCIONES CONVENCIONALES
5.1. Cadenas de Caracteres (Strings)
Las cadenas son secuencias de caracteres. Su implementación varía según lenguaje:
- C: Array de char terminado en ‘