15 preguntas de entrevista de C++ para principiantes — con respuestas que puedes decir en voz alta

Respuestas cortas y honestas a lo que de verdad preguntan en una entrevista junior de C++. Ejecuta los ejemplos mientras lees.

Lo básico del lenguaje

1. ¿Cuál es la diferencia entre un puntero y una referencia?

Una referencia es un alias: hay que ligarla a algo al crearla y nunca se puede reasignar ni dejar nula. Un puntero guarda una dirección: puede ser nulo, se puede reasignar y admite aritmética. Regla práctica: usa referencia cuando «tiene que haber un objeto»; usa puntero cuando «puede que no haya objeto» o necesitas moverte.

2. Pasar por valor contra pasar por referencia — ¿por qué importa?

Pasar por valor copia el argumento, así que los cambios dentro de la función no afectan a quien llama. Pasar por referencia le da a la función el objeto real; las estructuras grandes deberían pasarse por referencia const para evitar el coste de la copia. La demo de abajo muestra el contraste con un puntero (nuestro motor en el navegador todavía no soporta parámetros por referencia — la idea es la misma: darle a la función la dirección del objeto real en vez de una copia):

#include <iostream>
using namespace std;

void intentaCambiar(int x)      { x = 99; }    // copia — quien llama no se entera
void cambiaDeVerdad(int* x)     { *x = 99; }   // puntero al objeto real

int main() {
    int a = 1;
    intentaCambiar(a);
    cout << "después de intentaCambiar: " << a << endl;
    cambiaDeVerdad(&a);
    cout << "después de cambiaDeVerdad: " << a << endl;
    return 0;
}

3. ¿Qué significa const y por qué les encanta en las entrevistas?

const es una promesa: «esto no se va a modificar». const int* apunta a datos que no puedes cambiar a través del puntero; int* const es un puntero que no puede apuntar a otro sitio. Marcar parámetros y funciones miembro como const documenta la intención y deja que el compilador cace errores.

4. ¿Diferencia entre declaración y definición?

Una declaración dice «esto existe» (int add(int, int);); una definición aporta el cuerpo o el almacenamiento real (int add(int a, int b) { return a + b; }). Puedes declarar muchas veces y definir una sola. Por eso existen las cabeceras.

5. ¿Qué hace #include en realidad?

Pegar texto, literalmente: el preprocesador sustituye esa línea por el contenido de la cabecera antes de compilar. Por eso incluir la misma cabecera dos veces sin guardas de inclusión provoca errores de «redefinición».

Memoria — donde se separa a los juniors de los turistas

6. ¿Pila contra heap?

La pila guarda las variables locales; es rápida, se limpia sola y tiene tamaño limitado. El heap es para objetos cuyo tiempo de vida controlas tú (new/delete), reservar es más lento y ahí viven las fugas de memoria. El C++ moderno dice: prefiere el almacenamiento automático y, cuando necesites el heap, sé dueño de él mediante punteros inteligentes.

7. ¿Qué es una fuga de memoria, en una frase?

Memoria del heap que se reservó y que nunca se liberará porque perdiste la última forma de alcanzarla. Prevención: RAII — ata el tiempo de vida de cada recurso al ámbito de un objeto (eso es lo que hacen std::unique_ptr y std::vector).

8. ¿Qué devuelve new que malloc no devuelve?

Un puntero con el tipo correcto — y además ejecuta el constructor del objeto. malloc entrega bytes crudos sin inicializar (void*) y no sabe nada de constructores. En código moderno rara vez llamas a ninguno de los dos directamente.

Flujo de control y funciones

9. ¿Cuál es la diferencia entre ++i e i++?

Los dos suman uno. ++i (preincremento) incrementa y devuelve el valor nuevo; i++ (postincremento) devuelve el valor anterior e incrementa. En tipos clase, el preincremento puede ser más barato (sin copia temporal) — una pregunta de microdetalle muy típica.

10. ¿Qué es la recursión y qué coste esconde?

Una función que se llama a sí misma, con un caso base que la detiene. Coste escondido: cada llamada consume espacio de pila — una recursión sin un caso base ajustado desborda la pila (¡desbordamiento de pila!).

#include <iostream>
using namespace std;

int fib(int n) {
    if (n < 2) return n;              // caso base
    return fib(n - 1) + fib(n - 2);   // caso recursivo
}

int main() {
    for (int i = 0; i <= 8; i++) {
        cout << fib(i) << " ";
    }
    cout << endl;
    return 0;
}

La pregunta de seguimiento que te van a hacer: «¿cuál es la complejidad de este Fibonacci?». Respuesta: O(2ⁿ) — recalcula los mismos valores; arreglarlo con memoización o con iteración es la segunda mitad de la pregunta.

11. ¿Por qué main es especial?

Es el punto de entrada del programa, y el estándar garantiza dos firmas: int main() e int main(int argc, char* argv[]) (argumentos de línea de comandos). No hace falta llamarla — lo hace el entorno de ejecución.

Tipos y valores

12. División entera: ¿cuánto es 7 / 2 en C++?

3 — los dos operandos son int, así que el resultado es int (truncado hacia cero). 7.0 / 2 da 3.5 porque un operando es double. Fuente clásica de bugs, por eso es pregunta clásica.

13. ¿Qué es el desbordamiento de enteros y está definido?

Para enteros con signo, el desbordamiento es comportamiento indefinido — el compilador puede asumir que nunca ocurre y optimizar en consecuencia. Los enteros sin signo dan la vuelta módulo 2ⁿ por definición.

14. ¿Diferencia entre struct y class?

Casi ninguna: el acceso por defecto. Los miembros de un struct son públicos por defecto; los de una class, privados. Convención: struct para paquetes de datos simples, class cuando hay invariantes que proteger.

15. ¿Qué pasa si te olvidas de return 0; en main?

Nada malo — main es la única función que devuelve 0 de forma implícita. En cualquier otra, llegar al final de una función que no es void es comportamiento indefinido.

Cómo usar esta lista

Para seguir leyendo: C++ vector vs array — la pregunta de seguimiento sobre contenedores que casi siempre viene después.