
Pronto para mergulhar de cabeça no mundo corajoso da programação? Deseja ver como algumas linhas simples de código podem se comportar de forma imprevisível?
Se sua resposta for "Sim!" - bem vindo ao gato.
Você encontrará várias tarefas divertidas em C ou C ++.
A resposta correta com uma explicação sempre estará oculta sob o spoiler.
Boa sorte
Sobre o programa mais curto
main;
O que acontece se você compilar este programa com um compilador de linguagem C?
- Não compilado.
- Não está vinculado.
- Compilado e vinculado.
A resposta é:Este é um código C válido.
Porque Em C, você pode omitir o tipo de retorno de funções e, ao declarar variáveis, por padrão, será int. E também em C não há diferença entre funções e variáveis globais durante o vínculo. Nesse caso, o vinculador acha que, sob o nome main, é uma função.
Sobre garfo
#include <iostream> #include <unistd.h> int main() { for(auto i = 0; i < 1000; i++) std::cout << "Hello world!\n"; fork(); }
Quantas vezes o Hello World! Será impresso?
- 1000
- menos
- mais
A resposta é:As operações de E / S são armazenadas em buffer para melhorar o desempenho.
Chamar fork()
gerará um novo processo, com espaço de endereço duplicado para copiar na gravação.
Linhas em buffer serão impressas em cada processo.
Sobre índices
#include <iostream> int main() { int array[] = { 1, 2, 3 }; std::cout << (4, (1, 2)[array]) << std::endl; }
O que esse código será impresso?
- 1
- 2
- 3
- 4
- Erro de compilação
- Não definido pelo padrão.
A resposta é:O Porgram imprime 3.
Porque
Primeiro, observe o índice: array[index] == *(array + index) == *(index + array) == index[array]
Em seguida, estamos lidando com um operador de vírgula binária. Ele descarta seu argumento esquerdo e retorna o valor da direita.
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; }
Qual é o tempo mínimo nesta temporada regular?
- ~ 1 ms.
- ~ 100 ms.
- ~ 1 seg.
- ~ 1 min
- ~ 1 hora.
- ~ 1 ano.
- vida mais longa do universo.
A resposta é:Haha Isso não é adivinhado. Depende do compilador.
No meu laptop, clang mostra um resultado de cerca de 100 ms.
GCC 57 segundos! Um minuto! Sério ?!
Porque
Existem 2 abordagens para implementar expressões regulares.
Uma é transformar a expressão regular em uma máquina de estado em O(n**2)
, para uma expressão regular de comprimento n caracteres.
A dificuldade de combinar com uma sequência de caracteres m é O(m)
. Essa expressão regular não suporta retrocesso.
O segundo é um pouco de uma pesquisa gananciosa com pesquisa em profundidade. Suporta retrocesso.
E, no entanto, a complexidade das operações de expressão regular no STL ainda não está definida. É bom que eles conseguiram em um minuto.
Sobre move e 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; }
Qual linha o programa imprime por último?
Foo()
Foo(Foo&&)
Foo(const Foo&)
A resposta é:Foo(const Foo&)
. Por padrão, as lambdas são imunes. const
adicionado implicitamente a todos os valores especificados em []
.
Isso permite que as lambdas se comportem como funções normais. Para os mesmos argumentos, retorne os mesmos valores.
O que acontece neste caso? Quando tentamos fazer o movimento f
de uma função, obtemos const Foo&&
.
Isso é uma coisa muito estranha, o compilador não sabe como trabalhar com ele e copia o Foo
. Você pode corrigi-lo declarando uma lambda mutável:
auto a = [f = std::move(f)]() mutable { return std::move(f); };
Ou faça um construtor a partir de Foo(const Foo&&)
.
Sobre x e bar
#include <iostream> int x = 0; int bar(int(x)); int main() { std::cout << bar; }
O que acontece se você tentar compilar e executar isso?
- irá imprimir
0
- irá imprimir
1
- irá imprimir
0x0
- não compilado
- não ligado
A resposta é:O programa imprimirá 1
.
Porque
int bar(int(x));
É uma declaração de função, é equivalente a int bar(int x);
.
Se você deseja uma conversão de tipo, precisa escrever como esta int bar((int(x)));
.
Em seguida, tentamos produzir o endereço da função, que será implicitamente convertido em bool, o endereço da função não pode ser zero, ou seja, true
A função bar()
não é usada. Portanto, ao vincular, não haverá símbolo não referenciado.
Sobre inline
#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; }
O programa compila e vincula sem erros como este teste g++ -c main.cpp -o main.o && g++ foo.cpp -o foo.o && g++ foo.o main.o -o test
. O que acontece se você executá-lo?
- 120 será impresso.
- Tudo pode acontecer.
A resposta é:Tudo pode acontecer. Isso é C ++.
Todas as capturas na palavra inline. Esta é apenas uma indicação para o compilador.
Ele pode simplesmente compilar essa função em um arquivo de objeto (provavelmente, o fará para funções recursivas).
O vinculador pode lançar duplicatas de funções embutidas que não são incorporadas ao código.
O arquivo de resultado que geralmente aparece no primeiro arquivo é o que foi encontrado no primeiro arquivo de objeto.
O programa imprimirá 0
se estiver em 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 designers
#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(); }
Qual linha será impressa por último?
Foo(int, int)
Foo(const Foo&, int)
Foo(int, const Foo&)
Foo(int)
A resposta é:A última linha será Foo(const Foo&, int)
.
Foo(i)
é a declaração de uma variável, é equivalente a Foo i
, o que significa que o campo da classe i
desaparecerá do escopo.
Conclusão
Espero que você nunca veja isso em código real.