Björn Straustrup responde às 5 principais perguntas sobre C ++ com estouro de pilha

Antecipando o início do curso, "C ++ Developer" preparou uma tradução de material interessante.





Marielle Frank e Sonny Lee, autores do curso Learn C ++ na Codecademy, recentemente tiveram a oportunidade de entrevistar o Dr. Björn Straustrup, criador do C ++.

Como parte dessa entrevista, ele respondeu às perguntas em C ++ que obtiveram mais votos no Stack Overflow.Embora todas as entrevistas valam a pena ser lidas, o Codecademy nos permitiu generosamente compartilhar parte dela.

Se você já se perguntou se há respostas definitivamente abrangentes no Stack Overflow, aqui está algo mais próximo disso que você pode ter certeza (embora esperemos que alguém discorde).

Por que o processamento de uma matriz classificada é mais rápido que o processamento de uma matriz não classificada?



Nota: Esta pergunta é o número 1 que recebeu mais votos no estouro de pilha de todos os tempos.


Parece uma pergunta de uma entrevista. É verdade? Como você sabia disso? É ruim responder a perguntas sobre eficiência sem fazer medições preliminares, por isso é importante saber como medi-las.
Então, verifiquei um vetor de um milhão de números inteiros e obtive:

    32995 
          125944 

     18610 
          133304 

     17942 
          107858 


Eu executei isso algumas vezes para ter certeza. Sim, esse fenômeno é real. Meu código principal era:

void run(vector<int>& v, const string& label)
{
    auto t0 = system_clock::now();
    sort(v.begin(), v.end());
    auto t1 = system_clock::now();
    cout << label 
         << duration_cast<microseconds>(t1 — t0).count() 
         << " milliseconds\n";
}

void tst()
{
    vector<int> v(1'000'000);
    iota(v.begin(), v.end(), 0);
    run(v, "already sorted ");
    std::shuffle(v.begin(), v.end(), std::mt19937{ std::random_device{}() });
    run(v, "shuffled    ");
}


Pelo menos, esse fenômeno é real com este compilador, a biblioteca padrão e as configurações do otimizador. Implementações diferentes podem dar (e dar) respostas diferentes. De fato, alguém fez um estudo mais sistemático (uma pesquisa rápida na Internet o ajudará a encontrá-lo) e a maioria das implementações mostra esse efeito.

Um dos motivos é a previsão de ramificação: a operação principal no algoritmo de classificação é "se (v [i] <pivô]) ..." ou o equivalente. Para uma sequência classificada, esse teste é sempre verdadeiro, enquanto para uma sequência aleatória, o ramo selecionado muda aleatoriamente.

Outro motivo é que, quando o vetor já está classificado, nunca precisamos mover os elementos para a posição correta. O efeito desses pequenos detalhes fornece um fator de aproximadamente cinco ou seis, que observamos.

Quicksort (e classificação em geral) é um estudo complexo que atraiu algumas das maiores mentes da ciência da computação. Uma boa função de classificação é o resultado da escolha de um bom algoritmo e atenção ao desempenho do equipamento durante sua implementação.
Se você deseja escrever um código eficiente, é necessário levar em consideração a arquitetura da máquina.
————————————————————————————
Desculpe pela intervenção. Apenas um lembrete de que o podcast Stack Overflow está de volta! Visite e ouça uma entrevista com nosso novo CEO.

O que é o operador -> em C ++?




Esta é uma pergunta antiga e complicada. Em C ++, não há operador ->. Considere o seguinte:

if (p-->m == 0) f(p);


Obviamente, parece que existe algum tipo de operador -> e com uma declaração adequada p e m, você pode até compilar e executar isso:

int p = 2;
int m = 0;
if (p-->m == 0) f(p);


Na verdade, isso significa: veja se p-- é maior que m (do jeito que está) e compare o resultado (verdadeiro) com 0. Bem, verdadeiro! = 0, então o resultado é falso ef () não é chamado. Em outras palavras:

if ((p--) > m == 0) f(p);


Por favor, não gaste muito tempo com essas perguntas. Eles eram populares para iniciantes confusos, mesmo antes da invenção do C ++.

O melhor guia e lista de livros em C ++



Infelizmente, não existe uma lista canônica de livros em C ++. Isso, em princípio, não pode ser. Nem todo mundo precisa das mesmas informações, nem todo mundo tem a mesma experiência, e as melhores práticas de C ++ estão em constante evolução.

Fiz uma pequena pesquisa na Internet e encontrei um conjunto incrível de recomendações. Muitos estavam seriamente desatualizados, e alguns eram simplesmente ruins. Um iniciante que procura um bom livro sem orientação ficará muito confuso!

Você realmente precisa de um livro, porque os métodos que tornam o C ++ eficaz não são fáceis de encontrar em vários blogs sobre tópicos específicos e, é claro, os blogs também sofrem com erros, obsolescência e más explicações. Muitas vezes, eles também se concentram em novos tópicos avançados e ignoram os princípios básicos.

Eu recomendo meuProgramação: princípios e práticas do uso de C ++ (2ª edição) para pessoas que estão começando a aprender a programar, e o C ++ Tour (2ª edição) para pessoas que já são programadores e precisam se familiarizar com o C ++ moderno. Pessoas com uma sólida formação matemática podem começar com o artigo Descobrindo o C ++ moderno: um curso intensivo para cientistas, engenheiros e programadores Peter Gottschling.

Depois de começar a usar C ++ de verdade, você precisará de um conjunto de diretrizes para distinguir entre o que pode ser feito e o que é uma boa prática. Para isso, recomendo as Diretrizes Principais do C ++ no GitHub.

Para uma boa explicação breve dos recursos e funções individuais do idioma da biblioteca padrão, recomendowww.cppreference.com .

# 4 Quais são as diferenças entre uma variável de ponteiro e uma variável de referência em C ++?



Para saber mais sobre links e ponteiros, consulte Aprenda C ++ .

Ambos são representados na memória como um endereço de máquina. A diferença está no seu uso.
Para inicializar o ponteiro, você fornece o endereço do objeto:

int x = 7;
int* p1 = &x;
int* p2 = new int{9};


Para ler e escrever através de um ponteiro, usamos o operador de desreferência (*):

*p1 = 9;       // write through p1
int y = *p2;   // read through p2


Quando atribuímos um ponteiro a outro, ambos apontam para o mesmo objeto:

p1 = p2;       //  p1  p2    int   9
*p2 = 99;      //  99  p2
int z = *p1;   //   p1, z  99 (  9)


Observe que um ponteiro pode apontar para diferentes objetos durante seu ciclo de vida. Essa é a principal diferença dos links. O link é anexado ao objeto quando ele é criado e não pode ser convertido em um link para outro.

Para referências, a desreferenciação está implícita. Você inicializa o link com o objeto, e o link obtém seu endereço.

int x = 7;
int& r1 = x;
int& r2 = *new int{9};


O novo operador retorna um ponteiro, então tive que desreferenciá-lo antes da atribuição, usando-o para inicializar o link.

Para ler e escrever através de um link, simplesmente usamos o nome do link (sem desreferenciamento explícito):

r1 = 9;        //   r1
int y = r2;    //   r2


Quando atribuímos um link a outro, o valor especificado será copiado:

r1 = r2;       //  p1  p2     9
r1 = 99;       //  99  r1
int z = r2;    //   r2, z  9 (  99)


As referências e os ponteiros são frequentemente usados ​​como argumentos para uma função:

void f(int* p)

{
    if (p == nullptr) return;
    // ...
}

void g(int& r)
{
    // ...
}

int x = 7;
f(&x);
g(x);


Pode haver um ponteiro nullptr, então devemos verificar se ele aponta para alguma coisa. Sobre o link, você pode inicialmente assumir que ele se refere a algo.

# 5 Como iterar sobre palavras de seqüência de caracteres?




Use stringstream, mas como você define uma "palavra"? Pense: "Maria teve um cordeirinho". A última palavra é "cordeiro" ou "cordeiro".? Se não houver pontuação, é fácil:

vector<string> split(const string& s)
{
    stringstream ss(s);
    vector<string> words;
    for (string w; ss>>w; ) words.push_back(w);
    return words;
}

auto words = split("here is a simple example");   // five words
for (auto& w : words) cout << w << '\n';


ou simplesmente:

for (auto& w : split("here is a simple example")) cout << w << '\n';


Por padrão, o operador >> ignora espaços. Se precisarmos de conjuntos arbitrários de delimitadores, as coisas ficam um pouco mais confusas:

template<typename Delim>
string get_word(istream& ss, Delim d)
{
    string word;
    for (char ch; ss.get(ch); )    //  
        if (!d(ch)) {
            word.push_back(ch);
            break;
        }
    for (char ch; ss.get(ch); )    //  
        if (!d(ch))
            word.push_back(ch);
        else
            break;
    return word;
}


d é uma operação informando se o caractere é um delimitador e eu retorno "" (sequência vazia) para indicar que não havia palavra para retornar.

vector<string> split(const string& s, const string& delim)
{
    stringstream ss(s);
    auto del = [&](char ch) { for (auto x : delim) if (x == ch) return true; return false; };

    vector<string> words;
    for (string w; (w = get_word(ss, del))!= ""; ) words.push_back(w);
    return words;
}

auto words = split("Now! Here is something different; or is it? ", "!.,;? ");
for (auto& w : words) cout << w << '\n';


Se você possui uma biblioteca C ++ 20 Range, não precisa escrever algo assim, mas pode usar split_view.

Bjarn Straustrup é um parceiro técnico e diretor administrativo do Morgan Stanley New York e professor visitante na Columbia University. Ele também é o criador de C ++.

Para mais informações sobre C ++ 20, consulte: isocpp.org .


Isso é tudo. Vejo você no curso !

All Articles