¿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?
- No se compilará
- Se compilará, no se vinculará
- 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?
- 1000
- menos de 1000
- 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
- 2
- 3
- 4
- No se compilará
- 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 ms
- 1 segundo
- 1 minuto
- 1 hora
- 1 año
- 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…
-
Foo()
-
Foo(Foo&&)
-
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?
-
0
-
1
-
0x0
- No se compilará
- 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…
-
Foo(int, int)
-
Foo(const Foo&, int)
-
Foo(int, const Foo&)
-
Foo(int)
En lugar de conclusión
Espero que nunca encuentres uno de estos peculiares fragmentos en la naturaleza.
Respuestas
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 variablemain
de la funciónmain
.
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.
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 .
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 queindex[array
.La segunda pista es el operador
,
. Es un operador binario que descarta el argumento izquierdo y devuelve el argumento derecho.
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 cadenaO(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.
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ícitamenteconst
. Esto desbloquea el comportamiento idempotente de las lambdas.
Cuando mueves
f
creasconst Foo&&
.const Foo&&
es un tipo extraño, por lo tanto, el compilador simplemente copiaFoo
Hay dos formas de solucionar esto:
Crear lambda mutable
auto a = [f = std::move(f)]() mutable { return std::move(f); };
Declarar constructor
Foo(const Foo&&)
X y barra
El programa imprimirá
1
.
int bar(int(x));
— es una forma extraña de declarar una función, es igual aint 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 estrue
.
La función
bar()
nunca se ha utilizado, lo que nos permite evitar errores de símbolo no referenciado durante la vinculación.
Constructores
La última línea es
Foo(const Foo&, int)
.
Foo(i)
es una declaración de variable, lo mismo queFoo i
. Por lo tanto, el miembro de clase con el nombrei
está oculto en este ámbito.