
Prêt à plonger tête baissée dans le monde courageux de la programmation? Vous voulez voir comment quelques lignes de code simples peuvent se comporter de façon imprévisible?
Si votre réponse est "Oui!" - bienvenue au chat.
Vous trouverez plusieurs tâches amusantes en C ou C ++.
La bonne réponse avec une explication sera toujours cachée sous le spoiler.
Bonne chance
À propos du programme le plus court
main;
Que se passe-t-il si vous compilez ce programme avec un compilateur en langage C?
- Non compilé.
- Pas lié.
- Compilé et lié.
La réponse est:Il s'agit d'un code C valide.
Pourquoi? En C, vous pouvez omettre le type de retour des fonctions et lors de la déclaration des variables, par défaut, ce sera int. Et aussi en C il n'y a pas de différence entre les fonctions et les variables globales lors de la liaison. Dans ce cas, l'éditeur de liens pense que sous le nom main est une fonction.
À propos de fork
#include <iostream> #include <unistd.h> int main() { for(auto i = 0; i < 1000; i++) std::cout << "Hello world!\n"; fork(); }
Combien de fois Hello World! Sera-t-il imprimé?
- 1000
- moins
- plus
La réponse est:Les opérations d'E / S sont mises en mémoire tampon pour améliorer les performances.
L'appel de fork()
engendrera un nouveau processus, avec un espace d'adressage dupliqué sur écriture.
Des lignes tamponnées seront imprimées dans chaque processus.
À propos des index
#include <iostream> int main() { int array[] = { 1, 2, 3 }; std::cout << (4, (1, 2)[array]) << std::endl; }
Que va imprimer ce code?
- 1
- 2
- 3
- 4
- Erreur de compilation
- Non défini par la norme.
La réponse est:Impressions Porgram 3.
Pourquoi
Tout d'abord, regardez l'index: array[index] == *(array + index) == *(index + array) == index[array]
Ensuite, nous avons affaire à un opérateur virgule binaire. Il rejette son argument gauche et renvoie la valeur de droite.
À propos de regex
#include <regex> #include <iostream> int main() { std::regex re { "(.*|.*)*O" }; std::string str { "0123456789" }; std::cout << std::regex_match(str, re); return 0; }
Quelle est la durée minimale de cette saison régulière?
- ~ 1 ms.
- ~ 100 ms.
- ~ 1 sec.
- ~ 1 min
- ~ 1 heure.
- ~ 1 an.
- vie de l'univers plus longue.
La réponse est:Haha Ce n'est pas deviné. Dépend du compilateur.
Sur mon ordinateur portable, clang affiche un résultat d'environ 100 ms.
GCC 57 secondes! Une minute! Vraiment?!
Pourquoi
Il existe 2 approches pour implémenter des expressions régulières.
L'une consiste à transformer l'expression régulière en une machine à états dans O(n**2)
, pour une expression régulière de longueur n caractères.
La difficulté de correspondance avec une chaîne de m caractères est O(m)
. Une telle expression régulière ne prend pas en charge le retour en arrière.
La seconde est un peu une recherche gourmande avec recherche en profondeur. Prend en charge le retour en arrière.
Et pourtant, la complexité des opérations d'expression régulière en STL n'est pas du tout définie. C'est bien qu'ils aient réussi en une minute.
À propos de Move et 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; }
Quelle ligne le programme imprime-t-il en dernier?
Foo()
Foo(Foo&&)
Foo(const Foo&)
La réponse est:Foo(const Foo&)
. Par défaut, les lambdas sont immunisés. const
ajouté implicitement à toutes les valeurs spécifiées dans []
.
Cela permet aux lambdas de se comporter comme des fonctions normales. Pour les mêmes arguments, renvoyez les mêmes valeurs.
Que se passe-t-il dans ce cas? Lorsque nous essayons de déplacer f
d'une fonction, nous obtenons const Foo&&
.
C'est une chose très étrange, le compilateur ne sait pas comment l'utiliser et copie Foo
. Vous pouvez le corriger en déclarant un lambda mutable:
auto a = [f = std::move(f)]() mutable { return std::move(f); };
Ou créez un constructeur à partir de Foo(const Foo&&)
.
À propos de x et bar
#include <iostream> int x = 0; int bar(int(x)); int main() { std::cout << bar; }
Que se passe-t-il si vous essayez de compiler et d'exécuter cela?
- imprimera
0
- imprimera
1
- affichera
0x0
- non compilé
- pas lié
La réponse est:Le programme imprimera 1
.
Pourquoi
int bar(int(x));
Est une déclaration de fonction, elle équivaut à int bar(int x);
.
Si vous voulez un cast de type, vous devez écrire comme ceci int bar((int(x)));
.
Ensuite, nous essayons de sortir l'adresse de la fonction, elle sera implicitement castée en bool, l'adresse de la fonction ne peut pas être nulle, c'est-à-dire true
La fonction bar()
n'est pas utilisée. Par conséquent, lors de la liaison, il n'y aura pas de symbole non référencé.
À propos en ligne
#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; }
Le programme compile et lie sans erreurs comme ce g++ -c main.cpp -o main.o && g++ foo.cpp -o foo.o && g++ foo.o main.o -o test
. Que se passe-t-il si vous l'exécutez?
- 120 s'imprime.
- Tout peut arriver.
La réponse est:Tout peut arriver. C'est C ++.
Toutes les prises dans le mot en ligne. Ceci n'est qu'une indication pour le compilateur.
Il peut simplement compiler cette fonction dans un fichier objet (très probablement, il le fera pour les fonctions récursives).
L'éditeur de liens peut jeter des doublons de fonctions en ligne qui ne sont pas intégrées dans le code.
Le fichier de résultat qui apparaît généralement dans le premier fichier est celui qui a été trouvé dans le premier fichier objet.
Le programme affichera 0
si dans 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); }
À propos des 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(); }
Quelle ligne sera imprimée en dernier?
Foo(int, int)
Foo(const Foo&, int)
Foo(int, const Foo&)
Foo(int)
La réponse est:La dernière ligne sera Foo(const Foo&, int)
.
Foo(i)
est la déclaration d'une variable, elle est équivalente à Foo i
, ce qui signifie que le champ de classe i
disparaîtra de la portée.
Conclusion
J'espère que vous ne verrez jamais cela dans du vrai code.