paint-brush
Joyas ocultas de C y C++ que probablemente no conocíaspor@udalov
11,167 lecturas
11,167 lecturas

Joyas ocultas de C y C++ que probablemente no conocías

por Ilia6m2024/08/09
Read on Terminal Reader

Demasiado Largo; Para Leer

C++ es un lenguaje fantástico con muchas funciones. He experimentado múltiples momentos de sorpresa a lo largo de mi carrera como desarrollador de C++. En esta historia, encontrarás los aspectos más entretenidos de la programación en C y C++ condensados en forma de pruebas. No hay forma de que puedas responder más de un par de preguntas correctamente.
featured image - Joyas ocultas de C y C++ que probablemente no conocías
Ilia HackerNoon profile picture
0-item
1-item

¿Estás listo para unirte al majestuoso mundo de la programación en C y C++? ¿Quieres cuestionar tu existencia después de unas simples líneas de C++?


Si tu respuesta es "¡Sí!", "Sí" o "¿Por qué no?", te invitamos a poner a prueba tus conocimientos. Te haremos varias preguntas relacionadas con C o C++.


Las respuestas y explicaciones correctas las encontrarás al final de la historia. ¡Buena suerte!

1. El programa más pequeño

 main;


¿Qué pasará si intentas compilar este programa usando el compilador C?

  1. No se compilará
  2. Se compilará, no se vinculará
  3. Se compilará y vinculará

2. El tenedor

 #include <iostream> #include <unistd.h> int main() { for(auto i = 0; i < 1000; i++) std::cout << "Hello world!\n"; fork(); }


¿Cuántas líneas imprimirá este programa?

  1. 1000
  2. menos de 1000
  3. Más de 1000

3. Todo lo que necesitas son índices

 #include <iostream> int main() { int array[] = { 1, 2, 3 }; std::cout << (4, (1, 2)[array]) << std::endl; }


¿Qué imprimirá este programa?

  1. 1
  2. 2
  3. 3
  4. 4
  5. No se compilará
  6. indefinido

4. Expresiones regulares

 #include <regex> #include <iostream> int main() { std::regex re("(.*|.*)*O"); std::string str("0123456789"); std::cout << std::regex_match(str, re); return 0; }


¿Cuánto tiempo tardará esta expresión regular en coincidir con esta cadena de entrada?


  1. 1 ms
  2. 1 segundo
  3. 1 minuto
  4. 1 hora
  5. 1 año
  6. para siempre

5. Movimientos y lambdas

 #include <iostream> struct Foo { Foo() { std::cout << "Foo()\n"; } Foo(Foo&&) { std::cout << "Foo(Foo&&)\n"; } Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; } }; int main() { Foo f; auto a = [f = std::move(f)]() { return std::move(f); }; Foo f2(a()); return 0; }


La última línea que imprimirá este programa es…


  1. Foo()
  2. Foo(Foo&&)
  3. Foo(const Foo&)

6. X y barra

 #include <iostream> int x = 0; int bar(int(x)); int main() { std::cout << bar; }


¿Qué imprimirá este programa?

  1. 0
  2. 1
  3. 0x0
  4. No se compilará
  5. No se vinculará

7. Constructores

 #include <iostream> struct Foo { Foo() { std::cout << "Foo()\n"; } Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; } Foo(int) { std::cout << "Foo(int)\n"; } Foo(int, int) { std::cout << "Foo(int, int)\n"; } Foo(const Foo&, int) { std::cout << "Foo(const Foo&, int)\n"; } Foo(int, const Foo&) { std::cout << "Foo(int, const Foo&)\n"; } }; void f(Foo) {} struct Bar { int i, j; Bar() { f(Foo(i, j)); f(Foo(i)); Foo(i, j); Foo(i); Foo(i, j); } }; int main() { Bar(); }


La última línea que imprimirá este programa es…

  1. Foo(int, int)
  2. Foo(const Foo&, int)
  3. Foo(int, const Foo&)
  4. Foo(int)

En lugar de conclusión

Espero que nunca encuentres uno de estos peculiares fragmentos en la naturaleza.

Respuestas

  1. El programa más pequeño


    Este es un código C legal. Se compilará y vinculará correctamente. Se bloqueará si intenta ejecutarlo. main; - es una variable global.


    En el código C se pueden omitir muchas cosas. Por ejemplo, se puede omitir el tipo de una variable global. De forma predeterminada, el compilador asumirá que este tipo es un int . Además, en C no se alteran los nombres (a diferencia de C++), por lo que al vincular no hay forma de distinguir la variable main de la función main .


    De esta forma, el compilador compilará código válido y el enlazador encontrará algo llamado main en el archivo objeto para enlazar un programa.


  2. El tenedor


    Esta es una característica más de POSIX que de C o C++. Las implementaciones de operaciones de E/S utilizan búferes para optimizar el rendimiento. Cuando se invoca fork , el sistema operativo creará un duplicado de copia en escritura de la memoria del proceso, los búferes de E/S probablemente también se duplicarán y las cadenas almacenadas en búfer probablemente se imprimirán más de 1000 veces .


  3. Todo lo que necesitas son índices


    La respuesta es 3


    Para entender este código, veamos más de cerca cómo funcionan los índices en C y C++: array[index] , es lo mismo que *(array + index) , es lo mismo que (index + array) y lo mismo que index[array .

    La segunda pista es el operador , . Es un operador binario que descarta el argumento izquierdo y devuelve el argumento derecho.


  4. Expresiones regulares


    ¡Es imposible predecir lo que sucederá! El comportamiento depende de la implementación.


    En mi entorno, este programa genera la excepción The complexity of an attempted match against a regular expression exceeded a pre-set level.


    Otras opciones probables son que el tiempo sea sorprendentemente largo o que funcione como se espera. Esto se debe a que hay dos enfoques posibles para implementar expresiones regulares.


    Primero, transformar expresiones regulares en autómatas finitos O(n**2) (n - longitud del patrón), hacer coincidir la cadena O(m) (m - longitud de la cadena). Este enfoque no admite el retroceso.


    Segundo enfoque codicioso + DFS, admite retroceso pero es propenso a una complejidad temporal exponencial en ciertos patrones.


  5. Movimientos y lambdas


    La respuesta es Foo(const Foo&) . Las lambdas son inmutables de forma predeterminada, todos los valores capturados en lambda con [] son implícitamente const . Esto desbloquea el comportamiento idempotente de las lambdas.


    Cuando mueves f creas const Foo&& . const Foo&& es un tipo extraño, por lo tanto, el compilador simplemente copia Foo


    Hay dos formas de solucionar esto:


    1. Crear lambda mutable

       auto a = [f = std::move(f)]() mutable { return std::move(f); };


    2. Declarar constructor Foo(const Foo&&)


  6. X y barra

    El programa imprimirá 1 .


    int bar(int(x)); — es una forma extraña de declarar una función, es igual a int bar(int x); .

    Si lo confundes con la conversión de tipo, int bar((int(x))); - esto es una conversión de tipo.


    Luego intentamos convertir implícitamente la dirección de función a bool , el resultado de dicha conversión siempre es true .


    La función bar() nunca se ha utilizado, lo que nos permite evitar errores de símbolo no referenciado durante la vinculación.


  7. Constructores

    La última línea es Foo(const Foo&, int) .


    Foo(i) es una declaración de variable, lo mismo que Foo i . Por lo tanto, el miembro de clase con el nombre i está oculto en este ámbito.