paint-brush
Versteckte Schätze von C und C++, die Sie wahrscheinlich nicht kennenvon@udalov
11,231 Lesungen
11,231 Lesungen

Versteckte Schätze von C und C++, die Sie wahrscheinlich nicht kennen

von Ilia6m2024/08/09
Read on Terminal Reader

Zu lang; Lesen

C++ ist eine großartige Sprache mit zu vielen Features. Ich habe während meiner Karriere als C++-Entwickler zahlreiche „WTF“-Momente erlebt. In dieser Geschichte finden Sie die unterhaltsamsten Aspekte der C- und C++-Programmierung, komprimiert in Form von Tests. Sie können unmöglich mehr als ein paar Fragen richtig beantworten.
featured image - Versteckte Schätze von C und C++, die Sie wahrscheinlich nicht kennen
Ilia HackerNoon profile picture
0-item
1-item

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?

  1. wird nicht kompiliert
  2. wird kompiliert, wird nicht verlinkt
  3. 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?

  1. 1000
  2. weniger als 1000
  3. 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. 1
  2. 2
  3. 3
  4. 4
  5. wird nicht kompiliert
  6. 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. 1 ms
  2. 1 Sek.
  3. 1 Minute
  4. 1 Stunde
  5. 1 Jahr
  6. 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 …


  1. Foo()
  2. Foo(Foo&&)
  3. 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?

  1. 0
  2. 1
  3. 0x0
  4. wird nicht kompiliert
  5. 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 …

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

Statt Schlussfolgerung

Ich hoffe, dass Sie niemals einen dieser eigenartigen Schnipsel in freier Wildbahn finden werden.

Antworten

  1. 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.


  2. 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 .


  3. 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.


  4. 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.


  5. 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:


    1. Veränderbares Lambda erstellen

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


    2. Deklarieren Sie den Konstruktor Foo(const Foo&&)


  6. 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.


  7. 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.