Björn Straustrup responde las 5 preguntas principales de C ++ con desbordamiento de pila

En previsión del comienzo del curso, "C ++ Developer" preparó una traducción de material interesante.





Marielle Frank y Sonny Lee, autores del curso Learn C ++ Codecademy, recientemente tuvieron la oportunidad de entrevistar al Dr. Björn Straustrup, creador de C ++.

Como parte de esta entrevista, respondió las preguntas de C ++ que obtuvieron la mayor cantidad de votos en Stack Overflow. Aunque vale la pena leer todas las entrevistas, Codecademy nos permitió compartir generosamente parte de ella.

Si alguna vez se ha preguntado si hay respuestas definitivamente completas sobre Stack Overflow, aquí hay algo más cercano a esto que puede obtener con seguridad (aunque esperamos que alguien esté en desacuerdo).

¿Por qué procesar una matriz ordenada es más rápido que procesar una matriz no ordenada?



Nota: Esta pregunta es la número 1 que recibió más votos en el Desbordamiento de pila de todos los tiempos.


Suena como una pregunta de una entrevista. ¿Es verdad? Cómo lo supiste? Es malo responder preguntas sobre eficiencia sin tomar medidas preliminares, por lo que es importante saber cómo medirlas.
Entonces, verifiqué un vector de un millón de enteros y obtuve:

    32995 
          125944 

     18610 
          133304 

     17942 
          107858 


Ejecuté esto varias veces para asegurarme. Sí, este fenómeno es real. Mi código principal fue:

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    ");
}


Al menos, este fenómeno es real con este compilador, la biblioteca estándar y la configuración del optimizador. Las diferentes implementaciones pueden dar (y dar) diferentes respuestas. De hecho, alguien hizo un estudio más sistemático (una búsqueda rápida en Internet lo ayudará a encontrarlo), y la mayoría de las implementaciones muestran este efecto.

Una de las razones es la predicción de bifurcación: la operación clave en el algoritmo de clasificación es "if (v [i] <pivot]) ..." o el equivalente. Para una secuencia ordenada, esta prueba siempre es verdadera, mientras que para una secuencia aleatoria, la rama seleccionada cambia aleatoriamente.

Otra razón es que cuando el vector ya está ordenado, nunca necesitamos mover los elementos a la posición correcta. El efecto de estos pequeños detalles da un factor de aproximadamente cinco o seis, que observamos.

Quicksort (y clasificación en general) es un estudio complejo que ha atraído a algunas de las mejores mentes de la informática. Una buena función de clasificación es el resultado de elegir un buen algoritmo y prestar atención al rendimiento del equipo durante su implementación.
Si desea escribir código eficiente, debe tener en cuenta la arquitectura de la máquina.
—————————————————————————————
Perdón por la intervención. Solo un recordatorio de que el podcast Stack Overflow está de vuelta! Pase y escuche una entrevista con nuestro nuevo CEO.

¿Cuál es el operador -> en C ++?




Esta es una vieja pregunta capciosa. En C ++ no hay operador ->. Considera lo siguiente:

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


Esto, por supuesto, parece que hay algún tipo de operador -> y con una declaración adecuada p y m, incluso puede compilar y ejecutar esto:

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


Esto realmente significa: ver si p-- es mayor que m (como está), y luego comparar el resultado (verdadero) con 0. Bueno, verdadero! = 0, entonces el resultado es falso y no se llama a f (). En otras palabras:

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


Por favor, no gaste demasiado tiempo en tales preguntas. Eran populares para principiantes confusos incluso antes de la invención de C ++.

La mejor guía y lista de libros de C ++



Desafortunadamente, no hay una lista canónica de libros de C ++. Esto, en principio, no puede ser. No todos necesitan la misma información, no todos tienen la misma experiencia, y las mejores prácticas de C ++ están en constante evolución.

Investigué un poco en Internet y encontré un increíble conjunto de recomendaciones. Muchos estaban seriamente desactualizados, y algunos simplemente eran malos. ¡Un principiante que busque un buen libro sin orientación estará muy confundido!

Realmente necesita un libro porque los métodos que hacen que C ++ sea efectivo no son fáciles de encontrar en varios blogs sobre temas específicos y, por supuesto, los blogs también adolecen de errores, obsolescencia y malas explicaciones. A menudo también se centran en nuevos temas avanzados e ignoran los principios básicos.

Recomiendo miProgramación: Principios y práctica del uso de C ++ (segunda edición) para personas que recién comienzan a aprender a programar, y el C ++ Tour (segunda edición) para personas que ya son programadores y que necesitan familiarizarse con C ++ moderno. Las personas con una sólida formación matemática pueden comenzar descubriendo C ++ moderno: un curso intensivo para científicos, ingenieros y programadores Peter Gottschling.

Una vez que comience a usar C ++ de verdad, necesitará un conjunto de pautas para distinguir entre lo que se puede hacer y lo que es una buena práctica. Para esto, recomiendo las Pautas principales de C ++ en GitHub.

Para una buena breve explicación de las características y funciones del lenguaje individual de la biblioteca estándar, recomiendowww.cppreference.com .

# 4. ¿Cuáles son las diferencias entre una variable de puntero y una variable de referencia en C ++?



Para obtener más información sobre enlaces y punteros, consulte Aprender C ++ .

Ambos están representados en la memoria como una dirección de máquina. La diferencia está en su uso.
Para inicializar el puntero, le das la dirección del objeto:

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


Para leer y escribir a través de un puntero, utilizamos el operador de desreferencia (*):

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


Cuando asignamos un puntero a otro, ambos apuntarán al mismo objeto:

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


Tenga en cuenta que un puntero puede apuntar a diferentes objetos durante su ciclo de vida. Esta es la principal diferencia de los enlaces. El enlace se adjunta al objeto cuando se crea y no se puede convertir en un enlace a otro.

Para las referencias, la desreferenciación está implícita. Inicializa el enlace con el objeto, y el enlace obtiene su dirección.

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


El nuevo operador devuelve un puntero, así que tuve que desreferenciarlo antes de la asignación, usándolo para inicializar el enlace.

Para leer y escribir a través de un enlace, simplemente usamos el nombre del enlace (sin referencia explícita):

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


Cuando asignamos un enlace a otro, el valor especificado se copiará:

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


Tanto las referencias como los punteros a menudo se usan como argumentos para una función:

void f(int* p)

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

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

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


Puede haber un puntero nullptr, por lo que debemos verificar si apunta a algo. Sobre el enlace, inicialmente puede suponer que se refiere a algo.

# 5. ¿Cómo iterar sobre palabras de cadena?




Uso stringstream, pero ¿cómo define una "palabra"? Piensa: "María tenía un corderito". La última palabra es "cordero" o "cordero". Si no hay puntuación, esto es 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';


o simplemente:

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


Por defecto, el operador >> omite espacios. Si necesitamos conjuntos arbitrarios de delimitadores, las cosas se vuelven un poco más 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 es una operación que indica si el carácter es un delimitador, y devuelvo "" (cadena vacía) para indicar que no había palabra para devolver.

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';


Si tiene una biblioteca C ++ 20 Range, no necesita escribir algo así, pero puede usar split_view.

Bjarn Straustrup es socio técnico y director gerente de Morgan Stanley New York y profesor visitante en la Universidad de Columbia. También es el creador de C ++.

Para obtener más información sobre C ++ 20, consulte: isocpp.org .


Eso es todo. ¡Nos vemos en el curso !

All Articles