Sind Sie bereit, in die majestätische Welt der C- und C++-Programmierung einzutauchen? Wollen Sie Ihre Existenz nach ein paar einfachen Zeilen C++ hinterfragen?
Wenn Ihre Antwort „Yeh!“, „Jep“ oder „Warum nicht?“ ist, können Sie Ihr Wissen gerne testen. Sie erhalten mehrere Fragen zu C oder C++.
Die richtigen Antworten und Erklärungen finden Sie am Ende der Geschichte. Viel Glück!
1. Das kleinste Programm
main;
Was passiert, wenn Sie versuchen, dieses Programm mit dem C-Compiler zu kompilieren?
- wird nicht kompiliert
- wird kompiliert, wird nicht verlinkt
- wird kompilieren und wird verknüpfen
2. Die Gabel
#include <iostream> #include <unistd.h> int main() { for(auto i = 0; i < 1000; i++) std::cout << "Hello world!\n"; fork(); }
Wie viele Zeilen druckt dieses Programm?
- 1000
- weniger als 1000
- mehr als 1000
3. Alles was Sie brauchen sind Indizes
#include <iostream> int main() { int array[] = { 1, 2, 3 }; std::cout << (4, (1, 2)[array]) << std::endl; }
Was wird dieses Programm drucken?
- 1
- 2
- 3
- 4
- wird nicht kompiliert
- undefiniert
4. Reguläre Ausdrücke
#include <regex> #include <iostream> int main() { std::regex re("(.*|.*)*O"); std::string str("0123456789"); std::cout << std::regex_match(str, re); return 0; }
Wie lange dauert es, bis dieser reguläre Ausdruck mit dieser Eingabezeichenfolge übereinstimmt?
- 1 ms
- 1 Sek.
- 1 Minute
- 1 Stunde
- 1 Jahr
- für immer
5. Bewegungen und 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; }
Die letzte von diesem Programm auszugebende Zeile ist …
-
Foo()
-
Foo(Foo&&)
-
Foo(const Foo&)
6. X und Balken
#include <iostream> int x = 0; int bar(int(x)); int main() { std::cout << bar; }
Was wird dieses Programm drucken?
-
0
-
1
-
0x0
- wird nicht kompiliert
- wird nicht verlinken
7. Konstruktoren
#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(); }
Die letzte von diesem Programm auszugebende Zeile ist …
-
Foo(int, int)
-
Foo(const Foo&, int)
-
Foo(int, const Foo&)
-
Foo(int)
Statt Schlussfolgerung
Ich hoffe, dass Sie niemals einen dieser eigenartigen Schnipsel in freier Wildbahn finden werden.
Antworten
Das kleinste Programm
Dies ist gültiger C-Code. Er wird erfolgreich kompiliert und verknüpft. Beim Versuch, ihn auszuführen, stürzt er ab.
main;
- ist eine globale Variable.
In C-Code können Sie viele Dinge weglassen. Sie können beispielsweise den Typ einer globalen Variable weglassen. Der Compiler geht standardmäßig davon aus, dass dieser Typ ein
int
ist. Außerdem gibt es in C keine Namensverfälschung (anders als in C++), sodass beim Verknüpfen die Variablemain
nicht von der Funktionmain
unterschieden werden kann.
Somit kompiliert der Compiler gültigen Code und der Linker findet etwas mit dem Namen
main
in der Objektdatei, um ein Programm zu verknüpfen.
Die Gabel
Dies ist eher eine POSIX-Funktion als eine C- oder C++-Funktion. Implementierungen von IO-Operationen verwenden Puffer zur Leistungsoptimierung. Wenn Sie
fork
aufrufen, erstellt das Betriebssystem ein Copy-on-Write-Duplikat des Prozessspeichers, die IO-Puffer werden wahrscheinlich ebenfalls dupliziert und gepufferte Zeichenfolgen werden wahrscheinlich mehr als 1000 Mal gedruckt .
Alles was Sie brauchen sind Indizes
Die Antwort ist 3
Um diesen Code zu verstehen, schauen wir uns genauer an, wie Indizes in C und C++ funktionieren:
array[index]
ist dasselbe wie*(array + index)
, ist dasselbe wie(index + array)
und dasselbe wieindex[array
.Der zweite Hinweis ist der Operator
,
. Sein binärer Operator verwirft das linke Argument und gibt das rechte Argument zurück.
Reguläre Ausdrücke
Es ist unmöglich vorherzusagen, was passieren wird! Das Verhalten hängt von der Umsetzung ab.
In meiner Umgebung löst dieses Programm die Ausnahme aus
The complexity of an attempted match against a regular expression exceeded a pre-set level.
Andere mögliche Optionen dauern überraschend lange oder funktionieren wie erwartet. Das liegt daran, dass es zwei mögliche Ansätze zur Implementierung regulärer Ausdrücke gibt.
Erstens: Transformieren Sie reguläre Ausdrücke in endliche Automaten
O(n**2)
(n - Länge des Musters), Match-StringO(m)
(m - Länge des Strings). Dieser Ansatz unterstützt kein Backtracking.
Zweitens: Greedy-Ansatz + DFS, unterstützt Backtracking, neigt aber bei bestimmten Mustern zu exponentieller Zeitkomplexität.
Bewegungen und Lambdas
Die Antwort lautet
Foo(const Foo&)
. Lambdas sind standardmäßig unveränderlich, alle mit[]
in Lambdas erfassten Werte sind implizitconst
. Dies schaltet idempotentes Verhalten für Lambdas frei.
Wenn Sie
f
verschieben, erstellen Sieconst Foo&&
.const Foo&&
ist ein seltsamer Typ, daher kopiert der Compiler einfachFoo
Es gibt zwei Möglichkeiten, dieses Problem zu beheben:
Veränderbares Lambda erstellen
auto a = [f = std::move(f)]() mutable { return std::move(f); };
Deklarieren Sie den Konstruktor
Foo(const Foo&&)
X und Balken
Das Programm gibt
1
aus.
int bar(int(x));
— ist eine seltsame Art, eine Funktion zu deklarieren, es ist gleichint bar(int x);
.Falls Sie es mit der Typumwandlung verwechseln:
int bar((int(x)));
– das ist eine Typumwandlung.
Dann versuchen wir, die Funktionsadresse implizit in
bool
umzuwandeln. Das Ergebnis einer solchen Umwandlung ist immertrue
“.
Die Funktion
bar()
wurde nie verwendet, wodurch wir beim Verknüpfen den Fehler „nicht referenziertes Symbol“ umgehen konnten.
Konstruktoren
Die letzte Zeile ist
Foo(const Foo&, int)
.
Foo(i)
ist eine Variablendeklaration, dasselbe wieFoo i
. Daher ist das Klassenmitglied mit dem Nameni
in diesem Bereich verborgen.