
Bereit, kopfüber in die mutige Welt des Programmierens einzutauchen? Möchten Sie sehen, wie sich einige einfache Codezeilen unvorhersehbar verhalten können?
Wenn Ihre Antwort "Ja!" - Willkommen bei Katze.
In C oder C ++ finden Sie mehrere unterhaltsame Aufgaben.
Die richtige Antwort mit einer Erklärung wird immer unter dem Spoiler versteckt.
Viel Glück
Über das kürzeste Programm
main;
Was passiert, wenn Sie dieses Programm mit einem C-Sprach-Compiler kompilieren?
- Nicht kompiliert.
- Nicht verknüpft.
- Zusammengestellt und verknüpft.
Die Antwort lautet:Dies ist ein gültiger C-Code.
Warum? In C können Sie den Rückgabetyp von Funktionen weglassen. Wenn Sie Variablen deklarieren, ist dies standardmäßig int. Und auch in C gibt es beim Verknüpfen keinen Unterschied zwischen Funktionen und globalen Variablen. In diesem Fall glaubt der Linker, dass unter dem Namen main eine Funktion ist.
Über Gabel
#include <iostream> #include <unistd.h> int main() { for(auto i = 0; i < 1000; i++) std::cout << "Hello world!\n"; fork(); }
Wie oft wird Hello World! Gedruckt?
- 1000
- weniger
- mehr
Die Antwort lautet:E / A-Vorgänge werden gepuffert, um die Leistung zu verbessern.
Durch Aufrufen von fork()
wird ein neuer Prozess mit doppeltem Adressraum zum Kopieren beim Schreiben erzeugt.
Bei jedem Vorgang werden gepufferte Zeilen gedruckt.
Über Indizes
#include <iostream> int main() { int array[] = { 1, 2, 3 }; std::cout << (4, (1, 2)[array]) << std::endl; }
Was wird dieser Code drucken?
- 1
- 2
- 3
- 4
- Kompilierungsfehler
- Nicht standardmäßig definiert.
Die Antwort lautet:Porgram druckt 3.
Warum so?
Schauen Sie sich zuerst den Index an: array[index] == *(array + index) == *(index + array) == index[array]
Als nächstes haben wir es mit einem binären Kommaoperator zu tun. Es verwirft sein linkes Argument und gibt den Wert des rechten zurück.
Über Regex
#include <regex> #include <iostream> int main() { std::regex re { "(.*|.*)*O" }; std::string str { "0123456789" }; std::cout << std::regex_match(str, re); return 0; }
Was ist die Mindestzeit, die diese reguläre Saison fängt?
- ~ 1 ms.
- ~ 100 ms.
- ~ 1 Sek.
- ~ 1 min
- ~ 1 Stunde.
- ~ 1 Jahr.
- längeres Universum Leben.
Die Antwort lautet:Ha ha! Das wird nicht erraten. Kommt auf den Compiler an.
Auf meinem Laptop zeigt Clang ein Ergebnis von ca. 100 ms.
GCC 57 Sekunden! Eine Minute! Wirklich ?!
Warum so?
Es gibt zwei Ansätze zum Implementieren regulärer Ausdrücke.
Eine besteht darin, den regulären Ausdruck in eine Zustandsmaschine in O(n**2)
für einen regulären Ausdruck mit einer Länge von n Zeichen umzuwandeln.
Die Schwierigkeit, mit einer Folge von m Zeichen O(m)
ist O(m)
. Ein solcher regulärer Ausdruck unterstützt kein Backtracking.
Die zweite ist eine gierige Suche mit Tiefensuche. Unterstützt das Zurückverfolgen.
Die Komplexität von Operationen mit regulären Ausdrücken in STL ist jedoch überhaupt nicht definiert. Es ist gut, dass sie es in einer Minute geschafft haben.
Über Bewegung und 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; }
Welche Zeile druckt das Programm zuletzt?
Foo()
Foo(Foo&&)
Foo(const Foo&)
Die Antwort lautet:Foo(const Foo&)
. Standardmäßig sind Lambdas immun. const
implizit zu allen in []
angegebenen Werten hinzugefügt.
Dadurch können sich Lambdas wie normale Funktionen verhalten. Geben Sie für dieselben Argumente dieselben Werte zurück.
Was passiert in diesem Fall? Wenn wir versuchen, f
von einer Funktion zu verschieben, erhalten wir const Foo&&
.
Dies ist eine sehr seltsame Sache, der Compiler weiß nicht, wie er damit arbeiten soll und kopiert Foo
. Sie können das Problem beheben, indem Sie ein veränderliches Lambda deklarieren:
auto a = [f = std::move(f)]() mutable { return std::move(f); };
Oder machen Sie einen Konstruktor aus Foo(const Foo&&)
.
Über x und bar
#include <iostream> int x = 0; int bar(int(x)); int main() { std::cout << bar; }
Was passiert, wenn Sie versuchen, dies zu kompilieren und auszuführen?
- druckt
0
- druckt
1
- druckt
0x0
- nicht kompiliert
- nicht verlinkt
Die Antwort lautet:Das Programm druckt 1
.
Warum so?
int bar(int(x));
Ist eine Funktionsdeklaration, entspricht sie int bar(int x);
.
Wenn Sie eine Typumwandlung wünschen, müssen Sie wie int bar((int(x)));
schreiben: int bar((int(x)));
.
Dann versuchen wir, die Adresse der Funktion auszugeben. Sie wird implizit in bool umgewandelt. Die Adresse der Funktion kann nicht Null sein, d. H. true
Die Funktion bar()
wird nicht verwendet. Daher wird beim Verknüpfen kein nicht referenziertes Symbol angezeigt.
Über Inline
#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; }
Das Programm kompiliert und verknüpft fehlerfrei wie diesen g++ -c main.cpp -o main.o && g++ foo.cpp -o foo.o && g++ foo.o main.o -o test
. Was passiert, wenn Sie es ausführen?
- 120 wird gedruckt.
- Alles kann passieren.
Die Antwort lautet:Alles kann passieren. Das ist C ++.
Der ganze Haken im Wort Inline. Dies ist nur ein Hinweis für den Compiler.
Diese Funktion kann einfach in eine Objektdatei kompiliert werden (höchstwahrscheinlich für rekursive Funktionen).
Der Linker kann Duplikate von Inline-Funktionen ausgeben, die nicht in den Code integriert sind.
Die Ergebnisdatei, die normalerweise in der ersten Datei angezeigt wird, wurde in der ersten Objektdatei gefunden.
Das Programm gibt 0
wenn in 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); }
Über Designer
#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(); }
Welche Zeile wird zuletzt gedruckt?
Foo(int, int)
Foo(const Foo&, int)
Foo(int, const Foo&)
Foo(int)
Die Antwort lautet:Die letzte Zeile ist Foo(const Foo&, int)
.
Foo(i)
ist die Deklaration einer Variablen, sie entspricht Foo i
, was bedeutet, dass das Feld der Klasse i
aus dem Bereich verschwindet.
Fazit
Ich hoffe, Sie sehen dies nie in echtem Code.