Cómo no escribir código

imagen


¿Listo para sumergirse de lleno en el valiente mundo de la programación? ¿Quiere ver cómo algunas líneas de código simples pueden comportarse de manera impredecible?


Si su respuesta es "¡Sí!" - Bienvenido al gato.


Encontrará varias tareas entretenidas en C o C ++.


La respuesta correcta con una explicación siempre estará oculta debajo del spoiler.


Buena suerte


Sobre el programa más corto


main; 

¿Qué sucede si compila este programa con un compilador de lenguaje C?


  1. No compilado
  2. No vinculado
  3. Compilado y vinculado.

La respuesta es:

Este es el código C válido.
Por qué En C, puede omitir el tipo de retorno de funciones y al declarar variables, por defecto será int. Y también en C no hay diferencia entre funciones y variables globales durante la vinculación. En este caso, el enlazador piensa que bajo el nombre main hay una función.


Sobre tenedor


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

¿Cuántas veces se imprimirá Hello World!


  1. 1000
  2. menos
  3. mas

La respuesta es:

Las operaciones de E / S están protegidas para mejorar el rendimiento.
Llamar a fork() generará un nuevo proceso, con espacio de direcciones duplicado de copia en escritura.
Las líneas almacenadas se imprimirán en cada proceso.


Sobre índices


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

¿Qué imprimirá este código?


  1. 1
  2. 2
  3. 3
  4. 4 4
  5. Error de compilación
  6. No definido por el estándar.

La respuesta es:

Impresiones de porgram 3.
Por qué
Primero, mire el índice: array[index] == *(array + index) == *(index + array) == index[array]
A continuación, tratamos con un operador de coma binario. Descarta su argumento izquierdo y devuelve el valor de la derecha.


Sobre regex


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

¿Cuál es el tiempo mínimo que capturará esta temporada regular?


  1. ~ 1 ms.
  2. ~ 100 ms.
  3. ~ 1 seg.
  4. ~ 1 min
  5. ~ 1 hora
  6. ~ 1 año.
  7. vida más larga del universo.

La respuesta es:

Jaja Eso no se adivina. Depende del compilador.
En mi computadora portátil, clang muestra un resultado de aproximadamente 100 ms.
¡CCG 57 segundos! Un minuto! ¿En serio?
Por qué
Hay 2 enfoques para implementar expresiones regulares.
Una es convertir la expresión regular en una máquina de estado en O(n**2) , para una expresión regular de longitud n caracteres.
La dificultad de hacer coincidir una cadena de m caracteres es O(m) . Dicha expresión regular no admite el retroceso.
El segundo es un poco de búsqueda codiciosa con búsqueda profunda. Soporta retroceso.
Y, sin embargo, la complejidad de las operaciones de expresión regular en STL no está definida en absoluto. Es bueno que lo hayan logrado en un minuto.


Sobre movimiento y lambda


 #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; } 

¿Qué línea imprime el programa por última vez?


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

La respuesta es:

Foo(const Foo&) . Por defecto, las lambdas son inmunes. const agrega implícitamente a todos los valores especificados en [] .
Esto permite que las lambdas se comporten como funciones normales. Para los mismos argumentos, devuelve los mismos valores.
¿Qué pasa en este caso? Cuando intentamos hacer el movimiento f desde una función, obtenemos const Foo&& .
Esto es algo muy extraño, el compilador no sabe cómo trabajar con él y copia Foo . Puede solucionarlo declarando una lambda mutable:


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

O haga un constructor de Foo(const Foo&&) .


Sobre xy bar


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

¿Qué sucede si intentas compilar y ejecutar esto?


  1. imprimirá 0
  2. imprimirá 1
  3. imprimirá 0x0
  4. no compilado
  5. no vinculado

La respuesta es:

El programa imprimirá 1 .
Por qué
int bar(int(x)); Es una declaración de función, es equivalente a int bar(int x); .
Si desea un tipo de conversión, necesita escribir como esta int bar((int(x))); .
Luego tratamos de generar la dirección de la función, se convertirá implícitamente en bool, la dirección de la función no puede ser cero, es decir true
La función bar() no se utiliza. Por lo tanto, al vincular no habrá símbolo sin referencia.


Acerca de en línea


 #include <iostream> inline size_t factorial(size_t n) { if (n == 0) return 1; return n * factorial(n - 1); } int main() { std::cout << factorial(5) << std::endl; } 

El programa compila y enlaza sin errores como este g++ -c main.cpp -o main.o && g++ foo.cpp -o foo.o && g++ foo.o main.o -o test . ¿Qué pasa si lo ejecutas?


  1. Se imprimirán 120.
  2. Cualquier cosa puede pasar.

La respuesta es:

Cualquier cosa puede pasar. Esto es C ++.
Toda la trampa en la palabra en línea. Esto es solo una indicación para el compilador.
Simplemente puede compilar esta función en un archivo objeto (lo más probable es que lo haga para funciones recursivas).
El enlazador puede arrojar duplicados de funciones en línea que no están integradas en el código.
El archivo de resultados que generalmente aparece en el primer archivo es el que se encontró en el primer archivo de objeto.
El programa imprimirá 0 si en foo.cpp:


 #include <cstddef> inline size_t factorial(size_t n) { if (n == 0) return 0; return 2 * n * factorial(n - 1); } int foo(size_t n) { return factorial(n); } 

Sobre diseñadores


 #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(); } 

¿Qué línea se imprimirá la última?


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

La respuesta es:

La última línea será Foo(const Foo&, int) .
Foo(i) es la declaración de una variable, es equivalente a Foo i , lo que significa que el campo de clase i desaparecerá del alcance.


Conclusión


Espero que nunca veas esto en código real.

Source: https://habr.com/ru/post/es413697/


All Articles