Los paradigmas más evaluados son el estructurado (secuencia, selección e iteración) y el orientado a objetos (clases, objetos, encapsulamiento, herencia y polimorfismo). La puesta en marcha del software sigue tres fases: compilación del código fuente a código objeto, ligado (enlazado) de librerías para resolver referencias externas y ejecución del programa resultante.
Los tipos de datos y operadores exigen precisión. Un entero con signo de 32 bits en complemento a dos abarca de -2,147,483,648 a 2,147,483,647 (de -2^31 a 2^31-1). El punto flotante de doble precisión (binary64, IEEE 754) usa 64 bits: 1 de signo, 11 de exponente y 52 de mantisa. En aritmética, la multiplicación y la división tienen mayor precedencia que la suma y la resta, por lo que 2 + 3 * 4 se evalúa como 14. Los operadores lógicos && y || usan evaluación de cortocircuito: en A && B no se evalúa B si A es falso, y en A || B no se evalúa B si A es verdadero. El post-incremento i++ devuelve el valor actual y luego incrementa (si i=5, j = i++ deja j=5 e i=6); el pre-incremento ++i incrementa primero.
Para la traza y depuración conviene recordar el comportamiento y el costo de las estructuras de datos:
En complejidad: la búsqueda binaria es O(log n) pero exige un arreglo previamente ordenado; quicksort promedia O(n log n) y degrada a O(n^2) en el peor caso; merge sort es O(n log n) siempre, con espacio adicional O(n). Toda función recursiva necesita al menos un caso base; sin él ocurre recursión infinita y desbordamiento de la pila (stack overflow). Al depurar, distinga los errores de sintaxis (violan las reglas del lenguaje y los detecta el compilador) de los errores de lógica (compilan pero producen resultados incorrectos, como el off-by-one).
1. De acuerdo con el teorema de programación estructurada (Böhm-Jacopini), cualquier algoritmo puede expresarse combinando únicamente tres estructuras de control básicas. ¿Cuáles son esas estructuras?
El teorema de Böhm-Jacopini demuestra que secuencia, selección (if/switch) e iteración (ciclos) bastan para expresar cualquier algoritmo, sin necesidad de saltos incondicionales (goto), que la programación estructurada busca evitar. (Böhm, C. y Jacopini, G. (1966), teorema de programación estructurada; Dijkstra, E. (1968), Go To Statement Considered Harmful.)
2. En el paradigma orientado a objetos, ¿cuál es la relación correcta entre una clase y un objeto?
Una clase define la estructura (atributos y métodos) que compartirán todos sus objetos; cada objeto es una instancia concreta creada a partir de esa clase, con sus propios valores de atributos. (Booch, G., Object-Oriented Analysis and Design with Applications, cap. 1-2 (conceptos fundamentales de la POO).)
3. Una clase declara todos sus atributos como privados y solo permite modificarlos mediante métodos públicos (setters) que validan el valor recibido antes de asignarlo. ¿Qué principio de la programación orientada a objetos se aplica en este diseño?
El encapsulamiento consiste en ocultar el estado interno de un objeto y exponer solo una interfaz pública controlada, como los métodos setter con validación descritos en el caso. (Booch, G., Object-Oriented Analysis and Design with Applications; Sommerville, I., Ingeniería de Software (principios de la POO).)
4. Una clase 'Vehiculo' define atributos y métodos comunes, y las clases 'Automovil' y 'Motocicleta' reutilizan esos atributos y métodos sin volver a escribirlos, además de agregar los suyos propios. ¿Qué relación existe entre 'Vehiculo' y las otras dos clases?
La herencia permite que una subclase (Automovil, Motocicleta) reutilice y extienda los atributos y métodos de una superclase (Vehiculo) sin duplicar código. (Booch, G., Object-Oriented Analysis and Design with Applications (jerarquías de clases en la POO).)
5. En un sistema, una lista contiene objetos de distintas subclases de 'Figura' (Circulo, Cuadrado, Triangulo). Al recorrer la lista se invoca el método 'calcularArea()' sobre cada objeto, y en cada caso se ejecuta la versión específica de esa subclase, determinada hasta el momento de la ejecución. ¿Qué mecanismo de la programación orientada a objetos permite este comportamiento?
El polimorfismo con enlace dinámico (late binding) resuelve en tiempo de ejecución cuál versión sobrescrita del método se invoca, según el tipo real del objeto y no según el tipo declarado de la referencia. (Meyer, B., Object-Oriented Software Construction (despacho dinámico en POO); Booch, G., Object-Oriented Analysis and Design.)
6. Una clase define dos métodos con el mismo nombre 'sumar', pero uno recibe dos números enteros y el otro recibe dos números decimales; el compilador decide cuál invocar según los tipos de los argumentos. ¿Qué concepto ilustra este caso?
La sobrecarga (overloading) permite varios métodos con el mismo nombre pero distinta firma (tipos o número de parámetros); se resuelve en tiempo de compilación, a diferencia de la sobreescritura, que redefine un método heredado. (Java Language Specification, §8.4.9 (sobrecarga de métodos); Booch, G., Object-Oriented Analysis and Design.)
7. ¿Cuál de las siguientes opciones NO corresponde a uno de los principios fundamentales de la programación orientada a objetos?
Encapsulamiento, herencia, polimorfismo y abstracción son los pilares clásicos de la POO; la compilación cruzada es una técnica para generar código ejecutable en otra plataforma, ajena a estos principios. (Booch, G., Object-Oriented Analysis and Design with Applications (pilares de la POO).)
8. Un equipo de desarrollo necesita que la clase 'Impresora' use las capacidades de la clase 'ConexionRed' (por ejemplo, para enviar trabajos de impresión), sin que 'Impresora' sea un tipo de 'ConexionRed'. Para evitar un acoplamiento rígido mediante herencia, la mejor práctica es que 'Impresora' incluya un objeto 'ConexionRed' como atributo. ¿Qué principio de diseño se aplica en esta solución?
Cuando la relación entre clases es 'tiene un' (has-a) y no 'es un' (is-a), se prefiere la composición —incluir un objeto como atributo— sobre la herencia, para reducir el acoplamiento entre clases. (Gamma, E. et al., Design Patterns: Elements of Reusable Object-Oriented Software (1994), principio «favorecer la composición sobre la herencia».)
9. Un editor de texto implementa la función 'deshacer' (Ctrl+Z), de manera que la última acción realizada por el usuario es siempre la primera en revertirse. ¿Qué estructura de datos es más adecuada para implementar esta función, y bajo qué política de acceso?
La función 'deshacer' requiere revertir primero la acción más reciente, es decir, la política LIFO (último en entrar, primero en salir) propia de una pila, en la que push agrega y pop retira el elemento más reciente. (Cormen, Leiserson, Rivest y Stein, Introduction to Algorithms, 3ª ed., cap. 10 (Estructuras de datos elementales, pilas).)
10. Un sistema de impresión en red recibe trabajos de varios usuarios y los procesa en el mismo orden en que llegaron: el primer trabajo enviado es el primero en imprimirse. ¿Qué estructura de datos y política corresponden a este comportamiento?
Procesar los trabajos en el mismo orden de llegada corresponde a la política FIFO (primero en entrar, primero en salir), propia de una cola, donde enqueue agrega al final y dequeue retira del frente. (Cormen, Leiserson, Rivest y Stein, Introduction to Algorithms, 3ª ed., cap. 10 (Estructuras de datos elementales, colas).)
11. Un arreglo clásico se declara con un tamaño fijo que no puede modificarse durante la ejecución del programa, mientras que un vector dinámico (arreglo redimensionable) puede crecer o reducirse en tiempo de ejecución conforme se agregan o eliminan elementos. En ambos casos, ¿cuál es el costo de acceder a un elemento conociendo su índice?
Tanto en un arreglo de tamaño fijo como en un vector dinámico (almacenamiento contiguo), el acceso por índice es O(1), pues la posición en memoria se calcula directamente con la dirección base y el índice, sin recorrer la estructura. (Cormen, Leiserson, Rivest y Stein, Introduction to Algorithms, 3ª ed., cap. 10 (acceso a arreglos, tiempo constante O(1)).)
12. El siguiente fragmento de código en C declara un arreglo e intenta recorrerlo: int arreglo[10]; for (int i = 0; i <= 10; i++) { arreglo[i] = i * 2; } ¿Cuál es el error de lógica en este ciclo?
Un arreglo de 10 elementos tiene índices válidos de 0 a 9; la condición 'i <= 10' permite que i valga 10, produciendo un acceso fuera de rango (error clásico off-by-one). (ISO/IEC 9899:2018 (C17), §6.5.2.1 (subíndices válidos de arreglo, de 0 a n-1).)
13. Un desarrollador necesita insertar repetidamente nuevos elementos al inicio de una colección y rara vez necesita acceder a un elemento por su posición. ¿Qué estructura ofrece la operación de inserción al frente más eficiente para este caso?
En una lista enlazada, insertar un nuevo nodo al frente solo implica actualizar un apuntador, con costo O(1); en un arreglo, insertar al inicio requiere desplazar todos los elementos, con costo O(n). (Cormen, Leiserson, Rivest y Stein, Introduction to Algorithms, 3ª ed., cap. 10 (listas enlazadas, inserción al frente O(1)).)
14. En un árbol binario de búsqueda se desea insertar el valor 45 partiendo de la raíz, cuyo valor es 50. La raíz tiene un subárbol izquierdo con valores menores a 50 y un subárbol derecho con valores mayores a 50. ¿Hacia qué subárbol debe descender el algoritmo de inserción desde la raíz?
En un árbol binario de búsqueda, todo valor menor que el nodo actual se ubica en el subárbol izquierdo; como 45 < 50, la inserción continúa por la izquierda, con costo proporcional a la altura del árbol, O(h). (Cormen, Leiserson, Rivest y Stein, Introduction to Algorithms, 3ª ed., cap. 12 (árboles binarios de búsqueda).)
15. Una tabla hash bien diseñada, con una función hash adecuada y un mecanismo de resolución de colisiones, ofrece un costo promedio O(1) para las operaciones de búsqueda, inserción y eliminación. Sin embargo, existe un escenario en el que estas operaciones se degradan a O(n). ¿Cuál es ese escenario?
El peor caso O(n) ocurre cuando múltiples claves colisionan y se acumulan en la misma posición (por ejemplo, en una cadena de colisión), obligando a recorrerlas todas para encontrar la buscada. (Cormen, Leiserson, Rivest y Stein, Introduction to Algorithms, 3ª ed., cap. 11 (tablas hash, promedio O(1) y peor caso O(n)).)
16. Un programador ordena un arreglo de un millón de elementos que ya está casi ordenado, usando una implementación de quicksort que siempre elige como pivote el primer elemento del subarreglo. ¿Qué complejidad tendrá este caso particular, y por qué?
Quicksort tiene complejidad promedio O(n log n), pero su peor caso es O(n^2); elegir el primer elemento como pivote sobre datos casi ordenados produce particiones desbalanceadas que degradan el rendimiento al peor caso, a diferencia de merge sort, que garantiza O(n log n) siempre. (Cormen, Leiserson, Rivest y Stein, Introduction to Algorithms, 3ª ed., cap. 7 (quicksort, promedio O(n log n), peor caso O(n^2)).)
17. En lenguajes como C, una cadena se representa internamente como un arreglo de caracteres. ¿Cómo determina una función como strlen() dónde termina el contenido válido de la cadena?
En C, una cadena es un arreglo de caracteres terminado por el carácter nulo '\0'; funciones como strlen() recorren el arreglo hasta encontrar ese carácter para determinar dónde termina el contenido válido. (ISO/IEC 9899:2018 (C17), §7.1.1 (representación de cadenas de caracteres terminadas en '\0').)
18. Un desarrollador necesita buscar repetidamente valores dentro de un arreglo de un millón de elementos que ya se encuentra ordenado. ¿Qué algoritmo de búsqueda conviene aplicar para minimizar el número de comparaciones?
Sobre un arreglo ya ordenado, la búsqueda binaria localiza un valor en O(log n) comparaciones, dividiendo el espacio de búsqueda a la mitad en cada paso; esta ventaja se pierde si el arreglo no está ordenado. (Cormen, Leiserson, Rivest y Stein, Introduction to Algorithms, 3ª ed., cap. 2, ejercicio 2.3-5 (búsqueda binaria O(log n)).)
19. La siguiente función recursiva calcula el factorial de un número, pero contiene un error de lógica: int factorial(int n) { return n * factorial(n - 1); } ¿Cuál es el error y qué consecuencia provoca al ejecutarse?
Toda función recursiva necesita al menos un caso base que detenga la recursión; sin él, la función se sigue llamando indefinidamente y agota la pila de llamadas (stack overflow), como ocurre aquí al faltar la condición 'if (n <= 1) return 1;'. (Cormen, Leiserson, Rivest y Stein, Introduction to Algorithms, 3ª ed., cap. 4 (necesidad de un caso base en la recursión).)
20. ¿Cuál es el resultado de evaluar la siguiente expresión aritmética, respetando las reglas de precedencia de operadores? resultado = 2 + 3 * 4;
La multiplicación tiene mayor precedencia que la suma, por lo que primero se calcula 3 * 4 = 12 y después se suma 2, dando 14; el valor 20 correspondería a evaluar la expresión de izquierda a derecha sin respetar la precedencia. (ISO/IEC 9899:2018 (C17), §6.5 (precedencia y asociatividad de operadores aritméticos).)
21. Se ejecuta el siguiente fragmento de código, donde i inicia con el valor 5: int i = 5; int j = i++; int k = ++i; ¿Cuáles son los valores finales de i, j y k?
El post-incremento (i++) asigna a j el valor actual de i (5) y luego incrementa i a 6; el pre-incremento (++i) incrementa i primero (a 7) y luego asigna ese nuevo valor a k, quedando i=7, j=5, k=7. (ISO/IEC 9899:2018 (C17), §6.5.2.4 (post-incremento) y §6.5.3.1 (pre-incremento).)
22. El siguiente fragmento de código verifica si un apuntador es válido antes de usarlo: if (apuntador != NULL && apuntador->valor > 0) { procesar(apuntador); } ¿Por qué esta condición evita un error en tiempo de ejecución cuando apuntador es NULL?
El operador && evalúa de izquierda a derecha con cortocircuito: si 'apuntador != NULL' es falso, no se evalúa 'apuntador->valor', evitando así la desreferencia de un apuntador nulo. (ISO/IEC 9899:2018 (C17), §6.5.13 (evaluación de cortocircuito del AND lógico).)
23. Una variable entera con signo de 32 bits, representada en complemento a dos, contiene el valor máximo posible: 2,147,483,647. El programa ejecuta la instrucción 'contador = contador + 1;'. Según la representación en complemento a dos, ¿qué valor toma contador tras esta operación?
El rango de un entero con signo de 32 bits en complemento a dos va de -2,147,483,648 a 2,147,483,647; al sumar 1 al valor máximo, el patrón de bits 'da la vuelta' (wrap-around) hasta el valor mínimo del rango. (Patterson y Hennessy, Computer Organization and Design, cap. 3 (rango de enteros de 32 bits en complemento a dos).)
24. El estándar IEEE 754 define el formato de punto flotante de doble precisión (binary64) utilizado por el tipo double en la mayoría de los lenguajes de programación. ¿Cómo se distribuyen sus 64 bits?
El formato binary64 de IEEE 754 usa 1 bit de signo, 11 bits de exponente y 52 bits de mantisa; el esquema de 8 bits de exponente y 23 de mantisa corresponde al formato de precisión simple (binary32), no al de doble precisión. (IEEE 754-2019, IEEE Standard for Floating-Point Arithmetic (formato binary64).)
25. Un programa calcula un resultado decimal mediante varias operaciones de punto flotante y luego lo compara así: if (resultado == 0.3) { imprimir("coincide"); } Aunque matemáticamente el resultado debería ser 0.3, la condición casi nunca se cumple. ¿Cuál es la causa más probable de este error de lógica?
Debido a que el formato IEEE 754 representa los números en base binaria, muchos valores decimales exactos (como 0.3) no tienen representación exacta y quedan con un pequeño error de redondeo, por lo que comparar con == casi nunca coincide; la práctica correcta es comparar con una tolerancia (épsilon). (IEEE 754-2019, IEEE Standard for Floating-Point Arithmetic (limitaciones de precisión en la representación binaria).)
26. El siguiente fragmento de código en C pretende verificar si la variable 'edad' es igual a 18, pero contiene un error que se convierte en un error de lógica silencioso: if (edad = 18) { imprimir("es mayor de edad"); } ¿Cuál de las siguientes opciones NO describe correctamente el problema de este código?
En C, 'edad = 18' es una asignación válida que sí compila y se ejecuta, tratando el valor 18 como verdadero dentro del if; el error es de lógica (confundir = con ==), no un rechazo del compilador. (ISO/IEC 9899:2018 (C17), §6.5.16 (asignación simple) y §6.5.9 (igualdad); confusión clásica entre = y == en condiciones.)
27. ¿Cuál es la secuencia correcta de fases para obtener un programa ejecutable a partir de código fuente en C?
El preprocesador expande macros e inclusiones, luego el compilador traduce el código a ensamblador, el ensamblador genera el código objeto y finalmente el ligador produce el ejecutable. (Aho, Lam, Sethi y Ullman, Compilers: Principles, Techniques, and Tools, 2ª ed., cap. 1 (fases de un compilador y cadena de herramientas de traducción))
28. ¿Cuál es la función principal del ligador (linker) durante la construcción de un programa?
El ligador combina los módulos objeto generados por el compilador y resuelve las referencias a funciones y variables definidas en otros archivos o librerías; traducir a ensamblador y expandir macros son tareas del compilador y del preprocesador. (Aho, Lam, Sethi y Ullman, Compilers: Principles, Techniques, and Tools, 2ª ed. (fases de compilación y ligado))
29. Un programador compila un archivo que invoca una función declarada en un encabezado, pero cuya implementación se encuentra en una librería que no fue especificada al construir el ejecutable. ¿En qué etapa se manifestará el error resultante?
El compilador acepta la declaración de la función, pero al ligar el programa no encuentra su definición en ningún módulo objeto ni librería, lo que produce un error de referencia indefinida. (ISO/IEC 9899:2018 (C17), §5.1.1.2 (Fases de traducción, fase de ligado))
30. Respecto al ligado de librerías durante la construcción de un programa, ¿cuál de las siguientes afirmaciones NO es correcta?
Esa descripción corresponde a una librería ligada dinámicamente, no a una estática: una librería estática se copia por completo dentro del ejecutable durante el ligado y no se carga ni se comparte en tiempo de ejecución. (Silberschatz, Galvin y Gagne, Operating System Concepts, 10ª ed. (ligado dinámico y librerías compartidas))
31. ¿En qué etapa del proceso de construcción se detecta un error de sintaxis, como la omisión de un punto y coma al final de una instrucción?
El análisis sintáctico que detecta este tipo de error ocurre durante la compilación, antes de generar el código objeto; el ligado y la carga trabajan sobre código ya compilado. (ISO/IEC 9899:2018 (C17), §5.1.1.2 (Fases de traducción))
32. Al construir un programa en C que utiliza la función sqrt() de la librería matemática, el proceso reporta el mensaje "undefined reference to 'sqrt'", aunque el código incluye correctamente el encabezado math.h. ¿Cuál es la causa más probable del error?
El encabezado solo declara el prototipo de la función; su implementación reside en la librería matemática, que debe indicarse explícitamente al ligador (por ejemplo, con -lm) para resolver la referencia. (ISO/IEC 9899:2018 (C17), §5.1.1.2 (Fases de traducción, fase de ligado))
33. Un programa se compila sin advertencias y se liga sin errores, pero al ejecutarse termina abruptamente con una violación de segmento al desreferenciar un apuntador. ¿A qué tipo de error corresponde esta falla?
El acceso inválido a memoria mediante un apuntador ocurre al ejecutar el programa, por lo que ni el compilador ni el ligador pueden detectarlo de antemano. (Silberschatz, Galvin y Gagne, Operating System Concepts, 10ª ed. (gestión de memoria y errores en tiempo de ejecución))
34. ¿Cuál es la función del cargador (loader) del sistema operativo antes de que inicie la ejecución de un programa?
El cargador coloca el ejecutable en memoria principal y transfiere el control a su punto de entrada para iniciar la ejecución; el ligado de referencias ya se realizó previamente. (Silberschatz, Galvin y Gagne, Operating System Concepts, 10ª ed. (carga de programas en memoria))
35. Analice el siguiente fragmento de código: ``` int resultado; resultado = 2 + 3 * 4; printf("%d", resultado); ``` ¿Qué valor imprime este fragmento?
La multiplicación tiene mayor precedencia que la suma, por lo que la expresión se evalúa como 2 + (3*4) = 2 + 12 = 14, y no como (2+3)*4 = 20. (ISO/IEC 9899:2018 (C17), §6.5 (precedencia y asociatividad de operadores))