Lectura pública del tema
1. Lenguajes de programación
1. Lenguajes de programación
🎯 Idea clave
- Un lenguaje de programación es una notación formal diseñada para expresar algoritmos ejecutables por ordenador.
- Sirve como vehículo de comunicación entre la intención humana y el procesamiento automático de la máquina.
- Se distingue de los lenguajes naturales por su sintaxis rigurosa y su semántica precisa sin ambigüedades.
- Su formalidad garantiza la traducción inequívoca a código binario y la reproducibilidad de resultados.
- Resulta fundamental en la Administración Pública para sistemas de información, interoperabilidad y servicios al ciudadano.
- Proporciona abstracciones que permiten gestionar la complejidad y organizar sistemas informáticos extensos.
📚 Desarrollo
Definición formal. Un lenguaje de programación constituye una notación formal diseñada específicamente para expresar algoritmos de manera que puedan ser ejecutados por un ordenador, directa o indirectamente. Funciona como el vehículo esencial mediante el cual los seres humanos comunican a las máquinas las instrucciones necesarias para resolver problemas diversos, automatizar procesos administrativos o construir sistemas informáticos complejos.
Sintaxis rigurosa y semántica precisa. A diferencia de los lenguajes naturales, que admiten ambigüedades y variaciones contextuales, los lenguajes de programación se caracterizan por una sintaxis estricta y una semántica exacta, completamente libre de ambigüedades. Esta condición de formalidad rigurosa es la que garantiza que los programas escritos puedan ser traducidos sin equívocos al lenguaje que la máquina entiende de manera nativa: el código binario.
Determinismo en la ejecución. La condición formal inherente a estos lenguajes garantiza que un mismo programa produzca, en condiciones equivalentes, exactamente los mismos resultados independientemente de quién lo ejecute, cuándo se realice la ejecución o qué operador gestione el sistema. Esta predictabilidad absoluta deriva directamente de la capacidad de traducción precisa al código binario nativo del procesador.
Capacidad descriptiva integral. Como sistema formal completo, permite describir no solo algoritmos, sino también estructuras de datos complejas, operaciones matemáticas, transformaciones lógicas y reglas de control de flujo precisas. Posibilita especificar detalladamente qué datos se manejan, qué operaciones se realizan sobre ellos, en qué orden específico se ejecutan y bajo qué condiciones determinadas.
Importancia en la Administración Pública. La relevancia de estos lenguajes es creciente y estratégica en el sector público español, ya que la prestación de servicios digitales al ciudadano, la gestión interna administrativa, la interoperabilidad entre diferentes administraciones territoriales y la implementación efectiva del Esquema Nacional de Interoperabilidad descansan completamente sobre sistemas de información construidos utilizando estos lenguajes formales.
Abstracción y modularidad. Proporcionan mecanismos sofisticados de abstracción que permiten ocultar detalles de bajo nivel y complejidad técnica innecesaria, facilitando sustancialmente la reutilización de código probado, la organización de sistemas extensos y la reducción sistemática de errores de programación. Esta capacidad permite diseñar programas mantenibles y razonar sobre comportamientos abstractos sin depender de las características físicas específicas del hardware subyacente.
Especificación ejecutable. Un programa escrito en un lenguaje de programación no debe entenderse como mero texto descriptivo, sino como una especificación ejecutable o traducible sometida rigurosamente a reglas léxicas, sintácticas y semánticas estrictas. Esta naturaleza dual como texto legible y como conjunto de instrucciones procesables constituye su característica definitoria fundamental respecto a otros sistemas de notación formal matemática.
Construcción de sistemas complejos. Sin la existencia de estos lenguajes formales, el desarrollo de software quedaría limitado a instrucciones de máquina extremadamente difíciles de leer, mantener y verificar por programadores humanos. Su disponibilidad permite construir aplicaciones administrativas robustas, gestores documentales eficientes y sistemas de tramitación electrónica con niveles razonables de complejidad funcional y fiabilidad operativa.
🧩 Elementos esenciales
- Notación formal: Sistema diseñado para expresar algoritmos que pueden ser ejecutados por un ordenador.
- Vehículo comunicativo: Medio a través del cual los humanos transmiten instrucciones a las máquinas para el procesamiento automático.
- Sintaxis rigurosa: Conjunto de reglas gramaticales estrictas que definen la estructura válida de los programas.
- Semántica precisa: Significado exacto y sin ambigüedades que asegura interpretación consistente por parte del compilador o intérprete.
- Código binario: Lenguaje nativo de la máquina al que se traducen los programas escritos en lenguaje de alto nivel.
- Determinismo: Capacidad de producir idénticos resultados bajo condiciones equivalentes de ejecución.
- Descripción de algoritmos: Posibilidad de especificar pasos, datos, operaciones y reglas de control de flujo.
- Modularidad: Organización del código en unidades independientes que facilitan el mantenimiento y la reutilización.
- Abstracción: Mecanismos para ocultar complejidad interna y detalles de bajo nivel del hardware.
- Interoperabilidad: Base técnica para la comunicación entre sistemas de diferentes administraciones públicas.
🧠 Recuerda
- Es una notación formal para expresar algoritmos ejecutables, no un lenguaje natural.
- La sintaxis rigurosa y la semántica precisa eliminan ambigüedades interpretativas.
- La traducción final siempre se dirige al código binario nativo del procesador.
- Garantiza reproducibilidad: mismas condiciones producen idénticos resultados.
- Esencial para la prestación de servicios digitales en la Administración Pública.
- Permite describir datos, operaciones, orden de ejecución y condiciones específicas.
- Facilita la construcción de sistemas complejos mediante mecanismos de abstracción.
- Soporta la interoperabilidad entre administraciones y el Esquema Nacional de Interoperabilidad.
2. Representación de tipos de datos
2. Representación de tipos de datos
🎯 Idea clave
- Un tipo de datos define el dominio de valores, las operaciones aplicables, el espacio en memoria y la representación interna como dimensiones inseparables.
- La taxonomía clásica distingue cuatro familias fundamentales: tipos primitivos, compuestos, punteros o referencias, y definidos por el usuario.
- Los tipos primitivos comprenden enteros, reales, booleanos y caracteres, cada uno con convenciones específicas de codificación binaria.
- Los tipos compuestos se dividen en estáticos, con tamaño fijado en compilación, y dinámicos, que crecen durante la ejecución mediante estructuras enlazadas.
- Un tipo abstracto de datos especifica qué operaciones realiza independientemente de cómo se almacenan físicamente los valores.
- La representación concreta depende del lenguaje, compilador, arquitectura hardware y runtime, afectando a la portabilidad entre plataformas.
📚 Desarrollo
Definición integral. Un tipo de dato comprende cuatro dimensiones inseparables: el dominio de valores posibles, el conjunto de operaciones aplicables, el espacio ocupado en memoria y la representación interna concreta. Esta definición abarca desde tipos simples hasta estructuras complejas que organizan la información en aplicaciones administrativas.
Familias taxonómicas. Los tipos se clasifican en primitivos o básicos (elementales indivisibles como enteros y booleanos), compuestos o estructurados (agregados de otros tipos), punteros y referencias (direccionamiento indirecto a memoria), y tipos definidos por el usuario (construcciones personalizadas mediante enumeraciones o genéricos).
Representación de primitivos. Los enteros con signo emplean la codificación en complemento a dos, disponibles en tamaños de 8, 16, 32 y 64 bits. Los números reales siguen el estándar IEEE 754 en formatos de precisión simple y doble. Los caracteres se codifican tradicionalmente en ASCII o en Unicode mediante UTF-8 y UTF-16.
Tipos compuestos estáticos. Agrupan valores bajo organizaciones fijas en tiempo de compilación: arrays con elementos contiguos indexados, registros o structs con campos heterogéneos, conjuntos sin repeticiones y ficheros que abstraen el acceso a datos persistentes. Su tamaño se determina estáticamente antes de la ejecución.
Tipos compuestos dinámicos. Crecen o decrecen durante la ejecución mediante punteros o referencias: listas enlazadas, pilas con disciplina LIFO, colas con disciplina FIFO, árboles jerárquicos, grafos conectados y tablas hash. Permiten estructuras flexibles adaptables a requisitos cambiantes del sistema.
Abstracción versus implementación. Un tipo abstracto de datos especifica matemáticamente qué valores almacena y qué operaciones ofrece, independientemente de los detalles físicos. La estructura de datos materializa cómo se almacenan internamente los valores, separando el qué del cómo mediante principios de encapsulamiento y ocultación de información.
Variabilidad según plataforma. La representación concreta depende del lenguaje de programación, del compilador o intérprete, de la arquitectura hardware y del entorno de ejecución. Algunos lenguajes como Java fijan tamaños precisos para sus tipos primitivos, mientras que C y C++ establecen mínimos y relaciones que permiten variación según la plataforma, lo que exige precaución para garantizar la portabilidad.
🧩 Elementos esenciales
- Dimensión completa: dominio, operaciones, espacio en memoria y representación interna conforman la definición inseparable de un tipo.
- Complemento a dos: método estándar para representar enteros con signo en arquitecturas modernas, facilitando operaciones aritméticas.
- IEEE 754: estándar que rige la representación de números reales en coma flotante con variantes de 32 y 64 bits.
- Codificación de caracteres: evolución desde ASCII de 8 bits hasta Unicode con formatos UTF-8, UTF-16 y UTF-32.
- Cadenas de texto: pueden ser terminadas en nulo, de longitud explícita, mutables o inmutables según el diseño del lenguaje.
- Arrays: estructuras contiguas en memoria con acceso indexado directo y orden row-major o column-major en matrices.
- Registros: agrupación de campos heterogéneos bajo un identificador común, accesibles por nombre.
- Punteros inteligentes: mecanismos como shared_ptr o unique_ptr que mitigan riesgos de gestión manual de memoria.
- Tipos algebraicos: combinan sumas (uniones discriminadas) y productos (tuplas) para enriquecer la expresividad del sistema de tipos.
- Comprobación de tipos: puede ser estática o dinámica, fuerte o débil, con inferencia automática o gradual según el lenguaje.
🧠 Recuerda
- Un tipo de dato define valores posibles y operaciones válidas sobre ellos.
- La taxonomía clásica distingue primitivos, compuestos, punteros y definidos por usuario.
- Los enteros con signo usan complemento a dos; los reales, el estándar IEEE 754.
- Los tipos compuestos estáticos tienen tamaño fijo; los dinámicos crecen en ejecución.
- Un TAD responde al qué; la estructura de datos, al cómo se implementa físicamente.
- La representación binaria varía según lenguaje, compilador y arquitectura hardware.
- Java fija tamaños precisos; C/C++ permiten variación según plataforma.
- Los punteros habilitan estructuras dinámicas pero requieren gestión cuidadosa de memoria.
- Los arrays ofrecen acceso directo O(1) mientras que las listas permiten inserciones flexibles.
- La abstracción separa la interfaz de la implementación concreta para facilitar el mantenimiento.
3. Operadores
3. Operadores
🎯 Idea clave
- Los operadores son símbolos o palabras reservadas que ejecutan operaciones sobre operandos para generar valores o producir efectos laterales en el programa.
- Se clasifican fundamentalmente por aridad (unarios, binarios y ternarios) y por la función específica que desempeñan (aritméticos, relacionales, lógicos, etc.).
- La precedencia establece el orden de evaluación entre operadores de distintos niveles, mientras que la asociatividad resuelve los empates entre operadores de igual precedencia.
- La asociatividad general es de izquierda a derecha, con excepciones cruciales en la asignación y la exponenciación que asocian de derecha a izquierda.
- Los lenguajes utilizan árboles de expresión o el algoritmo Shunting-Yard para evaluar expresiones complejas de forma sistemática.
- La sobrecarga de operadores permite redefinir su comportamiento para tipos de datos definidos por el usuario en lenguajes como C++, Python o C#.
📚 Desarrollo
Definición formal. Un operador es un símbolo o palabra reservada que indica al compilador o intérprete que debe realizar una operación determinada sobre uno o más operandos, devolviendo como resultado un valor o produciendo un efecto lateral. Constituyen piezas léxicas fundamentales junto con identificadores y constantes.
Clasificación por aridad. Los operadores se categorizan según el número de operandos que manipulan: los unarios actúan sobre uno solo (como el signo negativo o la negación lógica), los binarios sobre dos operandos (suma, resta, comparaciones) y los ternarios sobre tres (el operador condicional condición ? valor1 : valor2 presente en C, Java o JavaScript).
Tipos funcionales. Además de la aridad, se agrupan por la operación ejecutada: aritméticos para cálculos numéricos (+, -, *, /, %, **), relacionales para comparaciones booleanas (==, !=, <, >), lógicos para conjunciones, de asignación (=, +=), bit a bit, de acceso a miembros, de arrays, de punteros, condicional ternario, null-coalescing, optional chaining y de tipo, entre otros.
Precedencia y asociatividad. La precedencia determina qué operadores se evalúan primero, organizados en quince niveles desde paréntesis y accesos hasta la coma. La asociatividad resuelve el orden cuando dos operadores comparten precedencia: generalmente es izquierda-derecha (a - b - c como (a - b) - c), pero derecha-izquierda en asignación (a = b = c equivale a a = (b = c)) y exponenciación (2 3 2 = 2 ** 9 = 512 en Python).
Evaluación de expresiones. Los lenguajes evalúan expresiones empleando internamente árboles de expresión o aplicando el algoritmo Shunting-Yard de Dijkstra, que convierte notación infija (estándar) a postfija (polaca inversa). Existen también notaciones prefija (LISP) y postfija (Forth, HP calculadoras).
Sobrecarga y comportamiento. La sobrecarga permite redefinir operadores para tipos propios mediante mecanismos específicos (como métodos add en Python), admitiéndola C++, C#, Kotlin, Rust y Python, mientras Java solo permite el operador + para concatenación de cadenas. Es crucial distinguir entre igualdad de valor (==) e identidad de objetos (is en Python), y recordar que las conversiones implícitas pueden alterar resultados inesperadamente.
🧩 Elementos esenciales
- Operador: símbolo o palabra reservada que indica una operación sobre operandos devolviendo un valor o efecto lateral.
- Aridad: clasificación según número de operandos afectados: unarios (1), binarios (2), ternarios (3) y raramente n-arios.
- Operadores aritméticos: realizan cálculos numéricos sobre enteros o coma flotante (+, -, *, /, %, **).
- Operadores relacionales: comparan valores y devuelven booleanos (==, !=, <, >, <=, >=), distinguiendo entre igualdad débil y estricta en algunos lenguajes.
- Precedencia: jerarquía que determina el orden de evaluación entre operadores de distintos niveles (15 niveles típicos).
- Asociatividad: regla que resuelve el orden ante operadores de igual precedencia; izquierda-derecha generalmente, derecha-izquierda en asignación y exponenciación.
- Evaluación de cortocircuito: mecanismo que evita evaluar operandos innecesarios en operadores lógicos cuando el resultado ya está determinado.
- Notación infija: formato estándar donde el operador se sitúa entre los operandos (a + b).
- Algoritmo Shunting-Yard: método de Dijkstra para convertir expresiones de notación infija a postfija para su evaluación sistemática.
- Sobrecarga de operadores: capacidad de redefinir el comportamiento de operadores para tipos definidos por el usuario.
- Igualdad vs identidad: igualdad compara valores (==), identidad compara referencias de objetos (operador is en Python).
- Efectos laterales: modificaciones de estado producidas por operadores que complican el análisis de expresiones.
🧠 Recuerda
- Un operador actúa sobre operandos y forma expresiones.
- Los operadores se clasifican por aridad: unarios, binarios y ternarios.
- También se clasifican por función: aritméticos, relacionales, lógicos, asignación, bits, acceso y otros.
- La precedencia determina agrupación; la asociatividad resuelve empates entre operadores del mismo nivel.
- El orden de evaluación no siempre equivale a precedencia.
- La evaluación de cortocircuito evita evaluar operandos innecesarios en operadores lógicos.
- El símbolo = suele asignar valores; == suele comparar igualdad.
- Igualdad de valor e identidad no son lo mismo.
- Las conversiones implícitas pueden cambiar resultados y ocultar errores.
- Los efectos laterales complican expresiones y conviene controlarlos.
4. Instrucciones condicionales
4. Instrucciones condicionales
🎯 Idea clave
- Las instrucciones condicionales constituyen uno de los tres pilares fundamentales de la programación estructurada, junto con la secuencia y la iteración, permitiendo la ramificación del flujo de ejecución.
- Estas estructuras evalúan expresiones booleanas para decidir qué bloque de código ejecutar, transformando programas secuenciales en procesos capaces de tomar decisiones.
- Las formas básicas incluyen la instrucción if simple, la estructura if-else para alternativas binarias, y las cadenas if-else if-else para discriminaciones múltiples ordenadas.
- Existen mecanismos de selección múltiple sobre valores discretos mediante switch y case, así como técnicas avanzadas como el pattern matching en lenguajes modernos.
- Los operadores lógicos &&, || y ! permiten combinar condiciones, empleándose frecuentemente la evaluación en cortocircuito para construir guardas defensivas.
📚 Desarrollo
Pilares de la programación estructurada. Las instrucciones condicionales representan el pilar de la selección en la programación estructurada moderna, junto con la secuencia y la iteración. Su introducción sistemática respondió al paradigma iniciado por Dijkstra en 1968, quien propuso eliminar el salto incondicional en favor de construcciones más legibles y verificables como if y switch.
Definición y mecanismo. Una instrucción condicional evalúa una expresión booleana cuyo resultado determina el bloque de código a ejecutar. Esta expresión puede incluir variables, operadores relacionales y llamadas a funciones, traduciéndose internamente en saltos condicionales del procesador que el programador abstrae mediante condiciones de alto nivel.
Formas básicas de control. La estructura if ejecuta un bloque únicamente cuando la condición es verdadera. La variante if-else añade una alternativa excluyente, ejecutando el segundo bloque cuando la condición resulta falsa. Las cadenas else if permiten evaluar múltiples condiciones secuencialmente, ejecutando el primer bloque cuya condición se cumpla.
Selección múltiple y pattern matching. Para discriminar entre valores discretos específicos, los lenguajes ofrecen la instrucción switch con cláusulas case, caracterizada históricamente por el comportamiento de fall-through entre casos. Los lenguajes modernos incorporan pattern matching, que generaliza esta capacidad permitiendo descomponer estructuras, vincular variables y verificar exhaustividad.
Operadores condicionales compactos. El operador ternario proporciona una forma expresiva compacta del if-else para selección de valores. Los operadores null-aware ofrecen mecanismos defensivos para manejar referencias nulas sin anidamiento excesivo, resultando útiles en inicializaciones y accesos condicionales.
Evaluación en cortocircuito. Los operadores lógicos && y || implementan evaluación en cortocircuito, de modo que si el resultado puede determinarse evaluando solo el primer operando, el segundo no se procesa. Esta característica permite construir guardas defensivas como la verificación de nulos previa al acceso a miembros.
Pseudocódigo y buenas prácticas. En el pseudocódigo de oposiciones se representan mediante la estructura SI...ENTONCES...SINO...FIN SI. Es fundamental ordenar correctamente las condiciones cuando estas se solapan, utilizar guard clauses para reducir niveles de anidamiento, y prevenir errores frecuentes como la confusión entre igualdad y asignación.
🧩 Elementos esenciales
- Condición booleana: Expresión que evalúa a verdadero o falso y determina el flujo de ejecución, pudiendo incluir operadores relacionales y lógicos.
- If simple: Ejecuta un bloque de instrucciones únicamente si la condición evaluada resulta verdadera, omitiéndose en caso contrario.
- If-else: Proporciona dos caminos mutuamente excluyentes, ejecutando el bloque alternativo cuando la condición principal resulta falsa.
- If-else if-else: Cadena de discriminación que evalúa condiciones en orden secuencial, ejecutando el primer bloque cuya condición sea verdadera y descartando las restantes.
- Switch/case: Instrucción de selección múltiple sobre valores discretos que permite discriminar entre casos específicos, con comportamiento de fall-through implícito en muchos lenguajes.
- Pattern matching: Mecanismo avanzado presente en lenguajes como Rust, Scala o Kotlin que permite descomponer estructuras, vincular variables y aplicar guardas con verificación de exhaustividad.
- Operador ternario: Expresión condicional compacta de la forma condición ? valor_si : valor_no que devuelve valores según la condición evaluada.
- Operadores null-aware: Conjunto de operadores que facilitan el manejo defensivo de referencias nulas sin requerir anidamiento explícito.
- Evaluación en cortocircuito: Comportamiento de los operadores lógicos && y || que suspende la evaluación del segundo operando cuando el resultado puede determinarse por el primero.
- Guard clauses: Técnica de retorno temprano que reduce el anidamiento excesivo verificando condiciones excepcionales al inicio de funciones.
- Pseudocódigo estructurado: Representación mediante SI...ENTONCES...SINO...FIN SI, donde es crucial marcar claramente la correspondencia entre bloques en casos de anidamiento.
🧠 Recuerda
- La selección es uno de los tres pilares de la programación estructurada junto con la secuencia y la iteración.
- Las cadenas else if deben ordenarse cuidadosamente cuando las condiciones se solapan para garantizar el comportamiento deseado.
- El switch tradicional presenta fall-through implícito entre casos, requiriendo break explícito en lenguajes como C o Java.
- La evaluación en cortocircuito permite escribir guardas defensivas de forma segura verificando nulos antes de acceder a miembros.
- Los operadores ternarios y null-aware ofrecen alternativas compactas al if-else para asignaciones simples.
- El anidamiento excesivo dificulta la lectura y las pruebas, solucionándose mediante guard clauses.
- Cada rama condicional añade caminos de ejecución que deben probarse individualmente.
- Errores típicos incluyen confundir operadores de igualdad con asignación, tratar incorrectamente los límites y gestionar inadecuadamente valores nulos.
- En pseudocódigo de oposiciones se utiliza SI...ENTONCES...SINO...FIN SI, anotando valores antes y después de cada condición.
- Los lenguajes orientados a expresión permiten que if y match devuelvan valores, eliminando la necesidad de variables temporales.
5. Bucles y recursividad
5. Bucles y recursividad
🎯 Idea clave
- Los bucles y la recursividad son mecanismos de repetición que permiten ejecutar operaciones múltiples veces sin escribir código redundante.
- Un bucle requiere cuatro elementos esenciales: inicialización, condición, cuerpo y actualización para garantizar su correcta terminación.
- La recursividad resuelve problemas mediante funciones que se invocan a sí mismas sobre casos de tamaño decreciente hasta alcanzar un caso base.
- Todo algoritmo puede expresarse mediante secuencia, selección e iteración según el teorema de Böhm-Jacopini y la programación estructurada.
- La recursividad de cola permite optimización del compilador transformándola en iteración y evitando el desbordamiento de pila.
📚 Desarrollo
Mecanismos de repetición. Los bucles y la recursividad constituyen las dos formas fundamentales de repetición en programación. Mientras los bucles repiten un bloque de instrucciones mediante estructuras iterativas controladas por condiciones, la recursividad resuelve problemas mediante llamadas sucesivas de una función a sí misma sobre subproblemas de menor tamaño hasta alcanzar una solución directa.
Elementos del bucle. Todo bucle bien construido debe incorporar obligatoriamente cuatro componentes: la inicialización que prepara variables, la condición que decide la continuación, el cuerpo con las instrucciones repetidas y la actualización que modifica el estado avanzando hacia la terminación. La ausencia de actualización o una condición siempre verdadera genera bucles infinitos.
Tipos de iteración. Los lenguajes imperativos proporcionan construcciones distintas según el patrón de repetición. El bucle while evalúa la condición antes de cada iteración, pudiendo no ejecutarse ninguna vez. El do-while garantiza al menos una ejecución al evaluar la condición después. El for resulta idóneo para rangos numéricos o recorridos de colecciones.
Control de flujo. Las sentencias break y continue permiten alterar el comportamiento estándar de los bucles. Mientras break provoca la salida inmediata de la estructura iterativa, continue salta a la siguiente iteración sin completar el cuerpo actual. Los errores más frecuentes incluyen límites mal definidos y el off-by-one en el número de iteraciones.
Fundamentos de la recursividad. Una función recursiva requiere necesariamente dos partes: el caso base que detiene la recursión devolviendo un valor sin nueva invocación, y el caso recursivo donde la función se llama con argumentos que progresan hacia dicho caso base. Sin caso base o sin progresión hacia él, se produce desbordamiento de pila.
Formas de recursión. La recursividad puede ser directa cuando la función se invoca a sí misma, indirecta cuando lo hace a través de otras funciones, o mutua entre varias funciones. La recursión de cola ocurre cuando la llamada recursiva constituye la última operación de la función, permitiendo que el compilador la optimice transformándola en bucle y eliminando el riesgo de agotamiento de pila.
Base teórica. La programación estructurada, formulada por Dijkstra y Wirth y formalizada por el teorema de Böhm-Jacopini, establece que cualquier algoritmo puede expresarse mediante secuencia, selección e iteración, prescindiendo del goto. La recursividad aporta elegancia conceptual y resulta expresión natural de algoritmos divide y vencerás, aunque cada llamada consume un marco de activación en la pila.
🧩 Elementos esenciales
- Bucle while: estructura que evalúa la condición antes de cada iteración, sin garantizar ejecución mínima.
- Bucle do-while: construcción que ejecuta el cuerpo al menos una vez al evaluar la condición posteriormente.
- Bucle for: mecanismo optimizado para recorridos de rangos numéricos o colecciones con contador.
- Sentencia break: interrumpe inmediatamente la ejecución del bucle y sale de la estructura iterativa.
- Sentencia continue: finaliza la iteración actual y salta directamente a la siguiente evaluación de condición.
- Caso base: condición que detiene la recursión devolviendo un valor sin nueva llamada a la función.
- Caso recursivo: rama donde la función se invoca a sí misma con argumentos que acercan al caso base.
- Recursión de cola: forma de recursividad donde la llamada recursiva es la última operación, optimizable por el compilador.
- Stack overflow: error producido por recursión infinita o falta de progresión hacia el caso base que agota la pila de llamadas.
- Teorema de Böhm-Jacopini: formalización matemática que demuestra que todo algoritmo puede expresarse con secuencia, selección e iteración.
🧠 Recuerda
- Todo bucle necesita inicialización, condición, cuerpo y actualización para terminar correctamente.
- While puede ejecutarse cero veces; do-while garantiza al menos una ejecución.
- Break sale del bucle; continue salta a la siguiente iteración.
- La recursividad requiere caso base y progresión hacia él para evitar desbordamiento de pila.
- Cada llamada recursiva consume un marco de activación en la pila de llamadas.
- La recursión de cola permite optimización del compilador a iteración.
- Los errores off-by-one son frecuentes en la definición de límites de bucles.
- La recursividad directa, indirecta y mutua describe distintas formas de auto-invocación.
- Iteración y recursividad son formalmente equivalentes pero difieren en claridad y consumo de recursos.
- La programación estructurada fundamenta el uso de bucles frente al goto.
6. Procedimientos, funciones y parámetros
6. Procedimientos, funciones y parámetros
🎯 Idea clave
- Los subprogramas encapsulan bloques de instrucciones bajo un nombre para facilitar la reutilización y el mantenimiento del código.
- La distinción clásica entre procedimiento y función radica en que el primero realiza una acción sin devolver valor, mientras que la segunda retorna un resultado.
- Los parámetros formales definen la interfaz del subprograma, mientras que los argumentos son los valores concretos pasados en la llamada.
- Los mecanismos de paso de parámetros determinan si el subprograma recibe una copia del valor o una referencia al dato original.
- Las funciones puras, sin efectos laterales, son más sencillas de probar y mantener que aquellas que modifican el estado externo.
- Cada invocación crea un marco de activación que almacena los parámetros y variables locales durante la ejecución del subprograma.
📚 Desarrollo
Subprograma como unidad modular. Un subprograma constituye un bloque de instrucciones encapsulado bajo un nombre que puede parametrizarse y reutilizarse, conocido también como procedimiento, función, rutina o método. Su existencia fundamenta cuatro pilares esenciales: la modularidad al dividir problemas complejos, la reutilización mediante escritura única y múltiples invocaciones, la mantenibilidad al propagar correcciones automáticamente, y la abstracción al ocultar la implementación interna.
Evolución de la distinción histórica. En lenguajes clásicos como Pascal, Ada o Fortran, los procedimientos ejecutaban acciones sin retornar valores, mientras que las funciones devolvían resultados para expresiones. Los lenguajes modernos como C, Java, Python o C# han unificado ambos conceptos bajo el término función, utilizando tipos especiales como void, None o Unit cuando no producen valor de retorno, simplificando la sintaxis aunque diluyendo la expresividad académica original.
Estructura de definición e invocación. La declaración o prototipo especifica la firma sin cuerpo, mientras que la definición incorpora la implementación completa. Los componentes fundamentales incluyen el identificador, los parámetros formales, el tipo de retorno, el cuerpo ejecutable y modificadores de visibilidad como public, private o protected, además de calificadores como static, final, abstract o async según el lenguaje.
Diferencia entre parámetros y argumentos. Los parámetros formales aparecen en la definición del subprograma como variables locales que recibirán los datos, mientras que los argumentos o parámetros reales son los valores o expresiones concretas suministradas durante la llamada. Esta distinción conceptual es crucial para comprender el flujo de datos entre el programa llamador y el subprograma invocado.
Mecanismos de transferencia de datos. El paso por valor copia el contenido del argumento, protegiendo la variable original salvo que se trate de referencias a objetos mutables. El paso por referencia permite modificar directamente la variable del llamador mediante mecanismos como & en C++ o ref/out en C#. Existen además el paso por puntero, por nombre como en ALGOL 60, por necesidad con evaluación perezosa en Haskell, y por compartición en Java o Python donde se copian referencias.
Refinamientos en la definición de parámetros. Los lenguajes modernos admiten parámetros de salida explícitos (out), argumentos opcionales con valores por defecto, parámetros nombrados que permiten alterar el orden posicional, y parámetros variables o variádicos mediante constructos como varargs, *args o **kwargs. La sobrecarga de métodos, disponible en Java, C++ o C#, permite definir múltiples versiones de una función con distintos tipos o cantidad de parámetros.
Principios de diseño de subprogramas. Una función bien diseñada debe tener responsabilidad única y clara, minimizando efectos ocultos o laterales. Las funciones puras, que no modifican estado externo ni dependen de él, resultan significativamente más sencillas de probar y razonar que aquellas que producen mutaciones globales. El contrato documentado debe especificar claramente qué espera recibir y qué garantiza devolver.
Gestión de la memoria durante la ejecución. Cada llamada a un subprograma genera típicamente un marco de activación en la pila de ejecución que alberga los parámetros, las variables locales y la dirección de retorno. El ámbito determina la visibilidad de los identificadores, mientras que la vida del almacenamiento define cuánto persisten los datos en memoria durante y después de la ejecución del bloque.
🧩 Elementos esenciales
- Subprograma: Bloque de instrucciones encapsulado bajo un nombre que aporta modularidad, reutilización y abstracción al código.
- Procedimiento: Unidad de código que realiza una acción específica sin devolver un valor al punto de llamada en la terminología clásica.
- Función: Subprograma que devuelve un valor y puede utilizarse dentro de expresiones, aunque en lenguajes modernos todos los subprogramas reciben este nombre genéricamente.
- Parámetro formal: Variable definida en la cabecera del subprograma que actúa como receptora de los datos de entrada.
- Argumento: Valor o expresión concreta pasada durante la invocación que se vincula al parámetro formal correspondiente.
- Paso por valor: Mecanismo que copia el contenido del argumento, impidiendo que el subprograma modifique la variable original directamente.
- Paso por referencia: Mecanismo que permite al subprograma modificar directamente la variable del llamador al trabajar sobre su dirección de memoria.
- Parámetros variádicos: Constructos que permiten pasar un número indeterminado de argumentos mediante mecanismos como
*argsoparams. - Sobrecarga: Capacidad de definir múltiples funciones con el mismo nombre pero distinta firma de parámetros, presente en Java, C++ y C#.
- Función pura: Subprograma sin efectos laterales que, dados los mismos argumentos, siempre retorna el mismo resultado.
- Marco de activación: Estructura de memoria creada en cada llamada que almacena parámetros, variables locales e información de retorno.
🧠 Recuerda
- Un procedimiento realiza acciones; una función devuelve valores, aunque los lenguajes modernos tienden a unificar ambos conceptos.
- Los parámetros aparecen en la definición; los argumentos, en la llamada concreta.
- El paso por valor protege la variable original, mientras que el paso por referencia permite modificarla.
- En Java y Python se pasa por valor la referencia al objeto, permitiendo mutar el objeto pero no reasignar la variable externa.
- Las funciones puras son más fáciles de probar porque no dependen ni modifican el estado externo.
- Cada llamada crea un marco de activación con sus propias variables locales y parámetros.
- El ámbito determina dónde se ve un identificador; la vida, cuánto dura su almacenamiento en memoria.
- La sobrecarga permite múltiples versiones de una función con distintos parámetros, pero Python no la admite nativamente.
- Los parámetros opcionales con valor por defecto y los nombrados aumentan la flexibilidad de las llamadas.
- Un buen subprograma tiene responsabilidad única, pocos efectos ocultos y contrato claramente documentado.
7. Vectores y registros
7. Vectores y registros
🎯 Idea clave
- Los vectores almacenan elementos del mismo tipo accesibles mediante índice numérico, permitiendo acceso directo eficiente.
- Los registros agrupan campos heterogéneos identificados por nombre, proporcionando mayor claridad semántica que los vectores.
- Ambas estructuras son fundamentales para representar agregados de datos complejos en sistemas de información administrativa.
- La combinación de vectores y registros permite modelar colecciones de entidades compuestas como listas de expedientes o empleados.
- El acceso por índice en arrays con memoria contigua tiene complejidad constante O(1).
- Los errores típicos incluyen accesos fuera de rango y confusión entre paso por valor y por referencia.
📚 Desarrollo
Estructuras fundamentales. Los tipos de datos primitivos resultan insuficientes para modelar realidades complejas como nóminas o padrones. Los lenguajes ofrecen vectores para agrupar elementos homogéneos accesibles por posición numérica, y registros para agrupar campos heterogéneos identificados por nombre. Sobre estas dos abstracciones se construyen matrices, listas, tablas hash y bases de datos relacionales.
Características del vector. Un vector o array unidimensional almacena una secuencia ordenada de elementos del mismo tipo. Cada elemento ocupa una posición identificada por un índice que, en muchos lenguajes, comienza en cero, aunque algunos usan uno o rangos personalizados. Esta diferencia afecta directamente a los recorridos, límites y posibles errores de programación.
Representación contigua. La implementación clásica utiliza posiciones contiguas de memoria. Si todos los elementos tienen el mismo tamaño, la dirección de un elemento se calcula mediante la dirección base, el índice y el tamaño unitario. Esta propiedad permite acceso directo eficiente con complejidad O(1), sin necesidad de recorrer elementos previos.
Definición de registro. Un registro agrupa campos con nombre y tipos posiblemente distintos bajo una misma unidad lógica. A diferencia de los vectores, se accede por nombre de campo, no por índice uniforme. Esta estructura mejora la claridad semántica cuando los elementos tienen significados distintos, como una fecha con campos día, mes y año.
Vectores de registros. La combinación más frecuente es el array de registros o Array of Structs, donde cada elemento del vector es una instancia de registro con múltiples campos. Esta estructura representa colecciones de entidades homogéneas como listas de empleados o expedientes, permitiendo acceso mediante índice y campo específico.
Registros de arrays. La alternativa Struct of Arrays almacena vectores paralelos como campos de un registro, agrupando todos los valores del mismo tipo juntos. Esta disposición es más eficiente para procesamiento vectorial SIMD en CPU o GPU. Los registros pueden pasarse a funciones por valor, por referencia o mediante copia de referencia según el lenguaje.
Arrays multidimensionales. Estos generalizan la idea a más dimensiones, implementándose como arrays de arrays o bloques contiguos. Cuando la mayoría de posiciones están vacías, se prefieren estructuras dispersas que almacenan solo elementos relevantes mediante mapas o listas de coordenadas.
🧩 Elementos esenciales
- Vector: estructura que almacena elementos ordenados del mismo tipo accesibles mediante índice numérico.
- Índice base: valor inicial de indexación que varía según el lenguaje, siendo cero el más común en lenguajes modernos.
- Acceso directo: operación de complejidad O(1) posible en arrays con representación contigua en memoria.
- Registro: estructura con campos heterogéneos identificados por nombre que documentan el significado de cada dato.
- Array de Structs: vector cuyos elementos son registros completos, útil para procesar entidades enteras de forma natural.
- Struct of Arrays: registro cuyos campos son arrays paralelos, optimizado para operaciones sobre atributos específicos y procesamiento SIMD.
- Error de índice: acceso a posiciones fuera del rango válido que puede provocar excepciones o comportamientos inseguros según el lenguaje.
- Copia de registros: operación que puede realizarse por valor, implicando duplicación completa, o por referencia, compartiendo la misma instancia.
🧠 Recuerda
- Los índices en vectores suelen comenzar en cero, pero no es universal.
- El acceso por índice en arrays contiguos es O(1) porque se calcula la dirección directamente.
- Los registros mejoran la claridad cuando los campos tienen significados semánticos distintos.
- Un vector de registros representa una colección de entidades compuestas homogéneas.
- Insertar o borrar en medio de un vector requiere desplazar elementos, afectando al rendimiento.
- Los arrays multidimensionales pueden implementarse de forma densa o dispersa según el patrón de acceso.
- El paso de registros por valor implica copia completa, mientras que por referencia permite modificación externa y mejora el rendimiento con estructuras grandes.
- Los errores típicos son índices fuera de rango, campos no inicializados y mutabilidad compartida no deseada.
8. Estructura de un programa
8. Estructura de un programa
🎯 Idea clave
- La estructura de un programa es su organización lógica y física que facilita la lectura, el mantenimiento y la reutilización del código a lo largo del ciclo de vida del software.
- Un programa bien estructurado separa claramente la cabecera documental, las importaciones de dependencias, las declaraciones de tipos, los subprogramas y el punto de entrada.
- Cada lenguaje de programación impone un esqueleto particular que define cómo se organizan físicamente los ficheros y cómo se localiza el inicio de la ejecución.
- La modularización mediante paquetes, namespaces o módulos permite separar responsabilidades y gestionar dependencias entre componentes de forma controlada.
- Una arquitectura por capas y el cumplimiento de principios como SOLID garantizan la calidad estructural, la evolución sostenible y la reducción del coste total de propiedad.
📚 Desarrollo
Definición de estructura. La estructura de un programa es la organización interna que distribuye instrucciones, datos, funciones y módulos en una unidad coherente y mantenible. No se limita a la secuencia de órdenes, sino que establece cómo se relacionan los elementos, definiendo puntos de entrada, ámbitos de variables y separación de responsabilidades entre partes del sistema.
Bloques constitutivos. La mayoría de los programas comparten una estructura básica que incluye una cabecera con metadatos legales y descriptivos, seguida del bloque de importaciones que establece las dependencias externas. A continuación se sitúan las constantes y variables globales —cuyo uso debe minimizarse—, la declaración de tipos personalizados y, finalmente, los subprogramas y el punto de entrada que inicia la ejecución.
Particularidades sintácticas. Cada lenguaje impone su propio esqueleto estructural. C utiliza prototipos y una función main, Java organiza el código en packages y clases, Python emplea la construcción if __name__ == "__main__", C# utiliza namespaces y el método Main, mientras que Go y Rust definen paquetes y una función main específica que sirve como entrada al programa.
División en módulos. La modularización divide el programa en unidades manejables mediante mecanismos como paquetes en Java y Python, namespaces en C++, crates y mods en Rust o ficheros de cabecera en C. Estos elementos encapsulan funcionalidad, controlan la visibilidad entre componentes y facilitan la reutilización de código en distintos proyectos o contextos organizativos.
Organización arquitectónica. Los programas siguen arquitecturas por capas —presentación, lógica de negocio, acceso a datos e infraestructura— donde las dependencias fluyen siempre hacia capas inferiores. Se aplican patrones como MVC, MVP, MVVM, Clean Architecture, Hexagonal, Onion, CQRS o microservicios para separar responsabilidades y reducir el acoplamiento entre componentes del sistema.
Fundamentos de calidad. La estructura se rige por principios como SOLID —Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation y Dependency Inversion— junto con DRY, KISS, YAGNI, GRASP y la Ley de Demeter. Estos criterios garantizan alta cohesión, bajo acoplamiento y facilidad para realizar pruebas unitarias y evolucionar el software.
Estructura de directorios. La organización física del código en el sistema de ficheros refleja la arquitectura lógica mediante directorios como src/, test/, lib/, docs/ y build/. Ficheros de configuración como pom.xml, package.json, Cargo.toml o pyproject.toml gestionan dependencias y automatizan la construcción del proyecto según el ecosistema específico.
Flujo de ejecución básico. La estructura mínima de ejecución incluye declaraciones de elementos, inicialización de recursos, procesamiento de operaciones mediante secuencia, selección e iteración, y finalmente la salida de resultados. Esta separación clara facilita la depuración, el mantenimiento evolutivo y la comprensión del comportamiento del software.
🧩 Elementos esenciales
- Cabecera o preámbulo: sección inicial con comentarios de copyright, licencia, autoría y descripción del propósito del módulo.
- Bloque de importaciones: zona donde se declaran las dependencias externas, módulos del sistema y librerías de terceros necesarias para el funcionamiento.
- Variables y constantes globales: declaración de valores accesibles desde múltiples puntos del programa, minimizando su uso para evitar efectos laterales.
- Declaración de tipos: definición de clases, estructuras, enumeraciones, registros o uniones que representan las abstracciones del dominio.
- Punto de entrada: localización específica donde comienza la ejecución normal del programa, denominada main o similar según el lenguaje.
- Modularización: técnica de división en paquetes, namespaces, crates o módulos que encapsula funcionalidad y expone interfaces claras.
- Arquitectura por capas: organización en presentación, lógica de negocio, acceso a datos e infraestructura con dependencias dirigidas hacia abajo.
- Patrones arquitectónicos: soluciones estructurales como MVC, MVP, MVVM, Clean Architecture, Hexagonal, Onion, CQRS y microservicios.
- Principios SOLID: conjunto de cinco principios de diseño orientado a objetos que promueven la mantenibilidad y extensibilidad del código.
- Organización física: distribución en directorios src/, test/, lib/ y uso de ficheros de configuración específicos del ecosistema de desarrollo.
- Programación estructurada: paradigma basado en secuencia, selección e iteración sin uso de goto, favoreciendo la claridad del flujo de control.
- Paradigma orientado a objetos: estructuración mediante encapsulación, herencia, polimorfismo y abstracción para modelar entidades del dominio.
🧠 Recuerda
- Una buena estructura facilita la lectura, la depuración y la evolución del software a lo largo de su ciclo de vida.
- Las variables globales mutables deben minimizarse porque rompen la encapsulación y dificultan las pruebas unitarias.
- Cada lenguaje define su propio esqueleto estructural que determina cómo se organizan los ficheros y dónde comienza la ejecución.
- La alta cohesión y el bajo acoplamiento son indicadores fundamentales de una estructura correcta y mantenible.
- Los patrones arquitectónicos proporcionan soluciones probadas para organizar sistemas complejos de forma coherente.
- Los principios SOLID, junto con DRY y KISS, guían la toma de decisiones estructurales durante el desarrollo.
- La separación física en directorios específicos debe reflejar la arquitectura lógica del sistema.
- La gestión de errores debe considerarse parte integral del diseño estructural desde las primeras fases.
- Separar la lógica de negocio de la entrada/salida facilita la realización de pruebas automatizadas.
- En el ámbito de las Administraciones Públicas, una estructura clara permite la cesión y reutilización de sistemas por equipos cambiantes.