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 ?
- ne compilera pas
- compilera, ne liera pas
- 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 ?
- 1000
- moins de 1000
- 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
- 2
- 3
- 4
- ne compilera pas
- 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 ms
- 1 seconde
- 1 min
- 1 heure
- 1 an
- 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…
-
Foo()
-
Foo(Foo&&)
-
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 ?
-
0
-
1
-
0x0
- ne compilera pas
- 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…
-
Foo(int, int)
-
Foo(const Foo&, int)
-
Foo(int, const Foo&)
-
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
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 variablemain
de la fonctionmain
.
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.
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 .
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.
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îneO(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.
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 implicitementconst
. Cela débloque le comportement idempotent pour les lambdas.
Lorsque vous déplacez
f
vous créezconst Foo&&
.const Foo&&
est un type étrange, donc le compilateur copie simplementFoo
Il existe deux manières de résoudre ce problème :
Créer un lambda mutable
auto a = [f = std::move(f)]() mutable { return std::move(f); };
Déclarer le constructeur
Foo(const Foo&&)
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 toujourstrue
.
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.
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 dei
est masqué dans cette portée.