Como não escrever código

imagem


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?


  1. Não compilado.
  2. Não está vinculado.
  3. 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?


  1. 1000
  2. menos
  3. 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. 1
  2. 2
  3. 3
  4. 4
  5. Erro de compilação
  6. 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. ~ 1 ms.
  2. ~ 100 ms.
  3. ~ 1 seg.
  4. ~ 1 min
  5. ~ 1 hora.
  6. ~ 1 ano.
  7. 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?


  1. Foo()
  2. Foo(Foo&&)
  3. 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?


  1. irá imprimir 0
  2. irá imprimir 1
  3. irá imprimir 0x0
  4. não compilado
  5. 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?


  1. 120 será impresso.
  2. 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?


  1. Foo(int, int)
  2. Foo(const Foo&, int)
  3. Foo(int, const Foo&)
  4. 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.

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


All Articles