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!
main;
Was passiert, wenn Sie versuchen, dieses Programm mit dem C-Compiler zu kompilieren?
#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?
#include <iostream> int main() { int array[] = { 1, 2, 3 }; std::cout << (4, (1, 2)[array]) << std::endl; }
Was wird dieses Programm drucken?
#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?
#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&)
#include <iostream> int x = 0; int bar(int(x)); int main() { std::cout << bar; }
Was wird dieses Programm drucken?
0
1
0x0
#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)
Ich hoffe, dass Sie niemals einen dieser eigenartigen Schnipsel in freier Wildbahn finden werden.
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 Variable main
nicht von der Funktion main
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 wie index[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-String O(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 implizit const
. Dies schaltet idempotentes Verhalten für Lambdas frei.
Wenn Sie f
verschieben, erstellen Sie const Foo&&
. const Foo&&
ist ein seltsamer Typ, daher kopiert der Compiler einfach Foo
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 gleich int 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 immer true
“.
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 wie Foo i
. Daher ist das Klassenmitglied mit dem Namen i
in diesem Bereich verborgen.