paint-brush
Les trésors cachés du C et du C++ que vous ne connaissez probablement paspar@udalov
11,160 lectures
11,160 lectures

Les trésors cachés du C et du C++ que vous ne connaissez probablement pas

par Ilia6m2024/08/09
Read on Terminal Reader

Trop long; Pour lire

Le C++ est un langage formidable avec de nombreuses fonctionnalités. J'ai vécu de nombreux moments « WTF » tout au long de ma carrière de développeur C++. Dans cette histoire, vous trouverez les aspects les plus amusants de la programmation C et C++ condensés sous forme de tests. Il est impossible de répondre correctement à plus de deux questions.
featured image - Les trésors cachés du C et du C++ que vous ne connaissez probablement pas
Ilia HackerNoon profile picture
0-item
1-item

Etes-vous prêt à rejoindre le monde majestueux de la programmation C et C++ ? Voulez-vous remettre en question votre existence après quelques lignes simples de C++ ?


Si votre réponse est « Yeh ! », « Yep » ou « Pourquoi pas ? », n'hésitez pas à tester vos connaissances. Plusieurs questions liées au C ou au C++ vous seront posées.


Veuillez trouver les réponses et les explications correctes à la fin de l'histoire. Bonne chance !

1. Le plus petit programme

 main;


Que se passera-t-il si vous essayez de compiler ce programme à l'aide du compilateur C ?

  1. ne compilera pas
  2. compilera, ne liera pas
  3. compilera et liera

2. La fourchette

 #include <iostream> #include <unistd.h> int main() { for(auto i = 0; i < 1000; i++) std::cout << "Hello world!\n"; fork(); }


Combien de lignes ce programme imprimera-t-il ?

  1. 1000
  2. moins de 1000
  3. plus de 1000

3. Tout ce dont vous avez besoin, ce sont des indices

 #include <iostream> int main() { int array[] = { 1, 2, 3 }; std::cout << (4, (1, 2)[array]) << std::endl; }


Qu'est-ce que ce programme va imprimer ?

  1. 1
  2. 2
  3. 3
  4. 4
  5. ne compilera pas
  6. indéfini

4. Expressions régulières

 #include <regex> #include <iostream> int main() { std::regex re("(.*|.*)*O"); std::string str("0123456789"); std::cout << std::regex_match(str, re); return 0; }


Combien de temps faudra-t-il pour que cette expression régulière corresponde à cette chaîne d’entrée ?


  1. 1 ms
  2. 1 seconde
  3. 1 min
  4. 1 heure
  5. 1 an
  6. pour toujours

5. Déplacements et 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 dernière ligne à imprimer par ce programme est…


  1. Foo()
  2. Foo(Foo&&)
  3. Foo(const Foo&)

6. X et barre

 #include <iostream> int x = 0; int bar(int(x)); int main() { std::cout << bar; }


Qu'est-ce que ce programme va imprimer ?

  1. 0
  2. 1
  3. 0x0
  4. ne compilera pas
  5. ne sera pas lié

7. Constructeurs

 #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 dernière ligne à imprimer par ce programme est…

  1. Foo(int, int)
  2. Foo(const Foo&, int)
  3. Foo(int, const Foo&)
  4. Foo(int)

Au lieu de la conclusion

J’espère que vous ne trouverez jamais un de ces extraits particuliers dans la nature.

Réponses

  1. Le plus petit programme


    Il s'agit d'un code C légal. Il sera compilé et lié avec succès. Il plantera si vous essayez de l'exécuter. main; - est une variable globale.


    Dans le code C, vous pouvez omettre beaucoup de choses. Par exemple, vous pouvez omettre le type d'une variable globale. Par défaut, le compilateur supposera que ce type est un int . De plus, il n'y a pas de modification de nom en C (contrairement à C++), donc lors de la liaison, il n'y a aucun moyen de distinguer la variable main de la fonction main .


    Ainsi, le compilateur compilera un code valide et l'éditeur de liens trouvera quelque chose nommé main dans le fichier objet pour lier un programme.


  2. La fourchette


    Il s'agit davantage d'une fonctionnalité POSIX que d'une fonctionnalité C ou C++. Les implémentations d'opérations d'E/S utilisent des tampons pour optimiser les performances. Lorsque vous appelez fork , le système d'exploitation crée une copie en écriture de la mémoire du processus, les tampons d'E/S sont susceptibles d'être également dupliqués et les chaînes mises en mémoire tampon sont susceptibles d'être imprimées plus de 1 000 fois .


  3. Tout ce dont vous avez besoin, ce sont des indices


    La réponse est 3


    Pour comprendre ce code, examinons de plus près le fonctionnement des indices en C et C++ : array[index] , est identique à *(array + index) , est identique à (index + array) et identique à index[array .

    Le deuxième indice est l'opérateur , . Son opérateur binaire, il rejette l'argument de gauche et renvoie l'argument de droite.


  4. Expressions régulières


    Il est impossible de prédire ce qui va se passer ! Le comportement dépend de la mise en œuvre.


    Dans mon environnement, ce programme génère l'exception The complexity of an attempted match against a regular expression exceeded a pre-set level.


    D'autres options possibles sont un temps étonnamment long ou un fonctionnement conforme aux attentes. C'est parce qu'il existe deux approches possibles pour implémenter les expressions régulières.


    Premièrement, transformez les expressions régulières en automates finis O(n**2) (n - longueur du motif), faites correspondre la chaîne O(m) (m - longueur de la chaîne). Cette approche ne prend pas en charge le retour en arrière.


    Deuxièmement, l'approche gourmande + DFS, prend en charge le retour en arrière mais est sujette à une complexité temporelle exponentielle sur certains modèles.


  5. Mouvements et lambdas


    La réponse est Foo(const Foo&) . Les lambdas sont immuables par défaut, toutes les valeurs capturées dans lambda avec [] sont implicitement const . Cela débloque le comportement idempotent pour les lambdas.


    Lorsque vous déplacez f vous créez const Foo&& . const Foo&& est un type étrange, donc le compilateur copie simplement Foo


    Il existe deux manières de résoudre ce problème :


    1. Créer un lambda mutable

       auto a = [f = std::move(f)]() mutable { return std::move(f); };


    2. Déclarer le constructeur Foo(const Foo&&)


  6. X et barre

    Le programme imprimera 1 .


    int bar(int(x)); — est une manière étrange de déclarer une fonction, elle est égale à int bar(int x); .

    Si vous confondez avec le type cast, int bar((int(x))); - c'est un type cast.


    Ensuite, nous essayons de convertir implicitement l'adresse de la fonction en bool , le résultat d'une telle conversion est toujours true .


    La fonction bar() n'a jamais été utilisée, ce qui nous permet d'éviter l'erreur de symbole non référencé lors de la liaison.


  7. Constructeurs

    La dernière ligne est Foo(const Foo&, int) .


    Foo(i) est une déclaration de variable, identique à Foo i . Ainsi, le membre de classe sous le nom de i est masqué dans cette portée.