Vorwort
Java ist im Gegensatz zum Hype nichts anderes als C ++. Die Java-Hype-Maschine möchte, dass Sie glauben, dass die Sprachen ähnlich sind, da Java eine C ++ - ähnliche Syntax hat. Nichts kann weiter von der Wahrheit entfernt sein. Diese Fehlinformationen sind Teil des Grundes, warum Java-Programmierer zu C ++ wechseln und Java-ähnliche Syntax verwenden, ohne die Auswirkungen ihres Codes zu verstehen.
Weiter geht es
Aber ich kann nicht herausfinden, warum wir das so machen sollen. Ich würde annehmen, dass dies mit Effizienz und Geschwindigkeit zu tun hat, da wir direkten Zugriff auf die Speicheradresse erhalten. Habe ich recht?
Im Gegenteil. Der Heap ist viel langsamer als der Stack, da der Stack im Vergleich zum Heap sehr einfach ist. Bei automatischen Speichervariablen (auch als Stapelvariablen bezeichnet) werden die Destruktoren aufgerufen, sobald sie den Gültigkeitsbereich verlassen. Zum Beispiel:
{
std::string s;
}
// s is destroyed here
Wenn Sie dagegen einen dynamisch zugewiesenen Zeiger verwenden, muss sein Destruktor manuell aufgerufen werden. delete
ruft diesen Destruktor für Sie auf.
{
std::string* s = new std::string;
}
delete s; // destructor called
Dies hat nichts mit der new
in C # und Java vorherrschenden Syntax zu tun . Sie werden für ganz andere Zwecke eingesetzt.
Vorteile der dynamischen Allokation
1. Sie müssen die Größe des Arrays nicht im Voraus kennen
Eines der ersten Probleme, auf das viele C ++ - Programmierer stoßen, besteht darin, dass Sie, wenn sie willkürliche Eingaben von Benutzern akzeptieren, einer Stapelvariablen nur eine feste Größe zuweisen können. Sie können die Größe von Arrays auch nicht ändern. Zum Beispiel:
char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow
Wenn Sie std::string
stattdessen eine verwenden, std::string
ändert sich die Größe natürlich intern, sodass dies kein Problem darstellen sollte. Die Lösung für dieses Problem ist jedoch im Wesentlichen die dynamische Zuordnung. Sie können dynamischen Speicher basierend auf den Eingaben des Benutzers zuweisen, zum Beispiel:
int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];
Randnotiz : Ein Fehler, den viele Anfänger machen, ist die Verwendung von Arrays variabler Länge. Dies ist eine GNU-Erweiterung und auch eine in Clang, da sie viele der GCC-Erweiterungen widerspiegeln. Daher int arr[n]
sollte man sich nicht auf Folgendes
verlassen.
Da der Heap viel größer als der Stapel ist, kann man beliebig viel Speicher beliebig zuweisen / neu zuweisen, während der Stapel eine Einschränkung aufweist.
2. Arrays sind keine Zeiger
Wie ist das ein Vorteil, den Sie fragen? Die Antwort wird klar, sobald Sie die Verwirrung / den Mythos hinter Arrays und Zeigern verstanden haben. Es wird allgemein angenommen, dass sie gleich sind, aber nicht. Dieser Mythos beruht auf der Tatsache, dass Zeiger genau wie Arrays tiefgestellt werden können und aufgrund von Arrays in einer Funktionsdeklaration zu Zeigern auf der obersten Ebene zerfallen. Sobald jedoch ein Array in einen Zeiger zerfällt, verliert der Zeiger seine sizeof
Informationen. Damitsizeof(pointer)
wird die Größe des Zeigers in Bytes geben, die in der Regel 8 Bytes auf einem 64-Bit - System.
Sie können Arrays nicht zuweisen, sondern nur initialisieren. Zum Beispiel:
int arr[5] = {1, 2, 3, 4, 5}; // initialization
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
// be given by the amount of members in the initializer
arr = { 1, 2, 3, 4, 5 }; // ERROR
Auf der anderen Seite können Sie mit Zeigern machen, was Sie wollen. Leider verstehen Anfänger den Unterschied nicht, da die Unterscheidung zwischen Zeigern und Arrays in Java und C # von Hand erfolgt.
3. Polymorphismus
Java und C # verfügen über Funktionen, mit denen Sie Objekte als andere behandeln können, z. B. mithilfe des as
Schlüsselworts. Wenn also jemand ein Entity
Objekt als Player
Objekt behandeln möchte , kann man dies tun. Player player = Entity as Player;
Dies ist sehr nützlich, wenn Sie Funktionen für einen homogenen Container aufrufen möchten, die nur für einen bestimmten Typ gelten sollen. Die Funktionalität kann auf ähnliche Weise wie folgt erreicht werden:
std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
if (!test) // not a triangle
e.GenericFunction();
else
e.TriangleOnlyMagic();
}
Wenn also nur Triangles eine Rotate-Funktion hätten, wäre dies ein Compilerfehler, wenn Sie versuchen würden, sie für alle Objekte der Klasse aufzurufen. Mit dynamic_cast
können Sie das as
Schlüsselwort simulieren . Wenn ein Cast fehlschlägt, wird ein ungültiger Zeiger zurückgegeben. Dies !test
ist im Wesentlichen eine Abkürzung für die Überprüfung, obtest
NULL oder ein ungültiger Zeiger ist, was bedeutet, dass die Umwandlung fehlgeschlagen ist.
Vorteile automatischer Variablen
Nachdem Sie all die großartigen Möglichkeiten der dynamischen Zuweisung gesehen haben, fragen Sie sich wahrscheinlich, warum niemand die dynamische Zuweisung NICHT ständig verwenden sollte. Ich habe dir schon einen Grund gesagt, der Haufen ist langsam. Und wenn Sie nicht all diesen Speicher benötigen, sollten Sie ihn nicht missbrauchen. Hier sind einige Nachteile in keiner bestimmten Reihenfolge:
Es ist fehleranfällig. Die manuelle Speicherzuweisung ist gefährlich und Sie sind anfällig für Undichtigkeiten. Wenn Sie mit dem Debugger oder valgrind
(einem Tool für Speicherverluste) nicht vertraut sind , können Sie sich die Haare aus dem Kopf ziehen. Glücklicherweise lindern RAII-Redewendungen und intelligente Zeiger dies ein wenig, aber Sie müssen mit Praktiken wie der Dreierregel und der Fünf-Regel vertraut sein. Es gibt viele Informationen, die aufgenommen werden müssen, und Anfänger, die es entweder nicht wissen oder sich nicht darum kümmern, werden in diese Falle tappen.
Es ist nicht notwendig. Im Gegensatz zu Java und C #, wo es idiomatisch ist, das new
Schlüsselwort überall zu verwenden, sollten Sie es in C ++ nur verwenden, wenn Sie es benötigen. Der übliche Satz lautet: Wenn Sie einen Hammer haben, sieht alles wie ein Nagel aus. Während Anfänger , die mit C beginnen ++ sind von Zeigern Angst und lernen , durch Gewohnheit Stack - Variablen zu verwenden, Java und C # Programmierer beginnen mit Hilfe von Zeigern ohne es zu verstehen! Das bedeutet buchstäblich, auf dem falschen Fuß abzusteigen. Sie müssen alles aufgeben, was Sie wissen, denn die Syntax ist eine Sache, das Erlernen der Sprache eine andere.
1. (N) RVO - Aka, (benannt) Rückgabewertoptimierung
Eine Optimierung, die viele Compiler vornehmen, sind Elisions- und Rückgabewertoptimierungen . Diese Dinge können unnötige Kopien vermeiden, was für sehr große Objekte nützlich ist, wie z. B. einen Vektor, der viele Elemente enthält. Normalerweise besteht die übliche Praxis darin, Zeiger zum Übertragen des Eigentums zu verwenden, anstatt die großen Objekte zu kopieren, um sie zu verschieben . Dies hat zur Entstehung von Bewegungssemantik und intelligenten Zeigern geführt .
Wenn Sie Zeiger verwenden, tritt (N) RVO NICHT auf. Es ist vorteilhafter und weniger fehleranfällig, (N) RVO zu nutzen, als Zeiger zurückzugeben oder zu übergeben, wenn Sie sich Gedanken über die Optimierung machen. Fehlerlecks können auftreten, wenn der Aufrufer einer Funktion für die delete
Zuordnung eines dynamisch zugewiesenen Objekts und dergleichen verantwortlich ist. Es kann schwierig sein, den Besitz eines Objekts zu verfolgen, wenn Zeiger wie eine heiße Kartoffel herumgereicht werden. Verwenden Sie einfach Stapelvariablen, da dies einfacher und besser ist.