Warum eine Variable in einer Zeile deklarieren und in der nächsten zuweisen?


101

In C und C ++ sehe ich oft die folgende Konvention:

some_type val;
val = something;

some_type *ptr = NULL;
ptr = &something_else;

Anstatt von

some_type val = something;
some_type *ptr = &something_else;

Ich nahm anfangs an, dass dies eine Gewohnheit war, die von den Tagen übrig geblieben war, als Sie alle lokalen Variablen oben im Gültigkeitsbereich deklarieren mussten. Aber ich habe gelernt, die Gewohnheiten von erfahrenen Entwicklern nicht so schnell zu verwerfen. Gibt es also einen guten Grund, in einer Zeile zu deklarieren und danach zuzuweisen?


12
+1 für "Ich habe gelernt, die Gewohnheiten von erfahrenen Entwicklern nicht so schnell zu verwerfen." Das ist eine kluge Lektion zu lernen.
Wildcard

Antworten:


92

C

In C89 alle Erklärungen sein mußten zu Beginn eines Bereichs sein ( { ... }), aber diese Forderung wurde schnell fallen gelassen (zunächst mit Compiler - Erweiterungen und später mit dem Standard).

C ++

Diese Beispiele sind nicht gleich. some_type val = something;ruft den Kopierkonstruktor auf, während val = something;der Standardkonstruktor und dann die operator=Funktion aufgerufen werden. Dieser Unterschied ist oft kritisch.

Gewohnheiten

Einige Leute deklarieren lieber zuerst Variablen und definieren sie später, falls sie ihren Code später mit den Deklarationen an einer Stelle und der Definition an einer anderen neu formatieren.

In Bezug auf die Zeiger haben einige Leute die Gewohnheit, jeden Zeiger auf NULLoder zu initialisieren nullptr, unabhängig davon, was sie mit diesem Zeiger tun.


1
Großartige Auszeichnung für C ++, danke. Was ist mit C?
Jonathan Sterling

13
Die Tatsache, dass MSVC Deklarationen nur am Anfang eines Blocks unterstützt, wenn dieser im C-Modus kompiliert wird, ist für mich eine Quelle endloser Irritation.
Michael Burr

5
@Michael Burr: Das liegt daran, dass MSVC C99 überhaupt nicht unterstützt.
Orlp

3
"some_type val = something; ruft den Kopierkonstruktor auf": Er kann den Kopierkonstruktor aufrufen, aber der Standard erlaubt dem Compiler, die Standardkonstruktion eines Temporys, die Kopierkonstruktion von val und die Zerstörung des temporären und direkten Konstrukts von val mit a zu beseitigen some_typeKonstruktor somethingals einziges Argument. Dies ist ein sehr interessanter und ungewöhnlicher Randfall in C ++ ... es bedeutet, dass eine Vermutung über die semantische Bedeutung dieser Operationen besteht.

2
@Aerovistae: Für eingebaute Typen sind sie gleich, aber für benutzerdefinierte Typen kann nicht immer dasselbe gesagt werden.
Orlp

27

Sie haben Ihre Frage C und C ++ gleichzeitig markiert, während sich die Antwort in diesen Sprachen erheblich unterscheidet.

Erstens ist der Wortlaut des Titels Ihrer Frage falsch (oder genauer gesagt, für die Frage selbst irrelevant). In beiden Beispielen wird die Variable gleichzeitig in einer Zeile deklariert und definiert . Der Unterschied zwischen Ihren Beispielen besteht darin, dass in der ersten Variable die Variablen entweder nicht initialisiert oder mit einem Dummy-Wert initialisiert werden und dieser später ein aussagekräftiger Wert zugewiesen wird. Im zweiten Beispiel werden die Variablen sofort initialisiert .

Zweitens sind diese beiden Konstrukte in C ++ semantisch verschieden, wie @nightcracker in seiner Antwort feststellte. Der erste basiert auf der Initialisierung, der zweite auf der Zuweisung. In C ++ sind diese Operationen überladbar und können daher möglicherweise zu unterschiedlichen Ergebnissen führen (obwohl es keine gute Idee ist, nicht äquivalente Überladungen von Initialisierung und Zuweisung zu erzeugen).

In der ursprünglichen Standardsprache C (C89 / 90) ist es unzulässig, Variablen in der Mitte des Blocks zu deklarieren. Aus diesem Grund werden Variablen möglicherweise am Anfang des Blocks als nicht initialisiert (oder mit Dummy-Werten initialisiert) deklariert und dann sinnvoll zugewiesen Werte später, wenn diese aussagekräftigen Werte verfügbar werden.

In der Sprache C99 ist es in Ordnung, Variablen in der Mitte des Blocks zu deklarieren (genau wie in C ++). Dies bedeutet, dass der erste Ansatz nur in bestimmten Situationen erforderlich ist, in denen der Initialisierer zum Zeitpunkt der Deklaration nicht bekannt ist. (Dies gilt auch für C ++).


2
@ Jonathan Sterling: Ich habe deine Beispiele gelesen. Wahrscheinlich müssen Sie die Standardterminologie von C- und C ++ - Sprachen auffrischen. Insbesondere zu den Begriffen Deklaration und Definition , die in diesen Sprachen bestimmte Bedeutungen haben. Ich wiederhole es noch einmal: In beiden Beispielen werden die Variablen in einer Zeile deklariert und definiert. In C / C ++ deklariert und definiert die Zeile some_type val;sofort die Variable . Das habe ich in meiner Antwort gemeint. val

1
Ich verstehe, was du dort meinst. Sie haben definitiv Recht damit, zu erklären und zu definieren, dass ich ziemlich bedeutungslos bin, wie ich sie verwendet habe. Ich hoffe, Sie akzeptieren meine Entschuldigung für den schlechten Wortlaut und den schlecht durchdachten Kommentar.
Jonathan Sterling

1
Wenn also der Konsens lautet, dass "Deklarieren" das falsche Wort ist, würde ich vorschlagen, dass jemand, der den Standard besser kennt als ich, die Wikibooks-Seite bearbeitet.
Jonathan Sterling

2
In jedem anderen Kontext wäre declare das richtige Wort, aber da declare ein genau definiertes Konzept mit Konsequenzen ist, können Sie es in C und C ++ nicht so locker verwenden, wie Sie es in anderen Kontexten könnten.
Orlp

2
@ybungalobill: Du liegst falsch. Deklaration und Definition in C / C ++ schließen sich nicht gegenseitig aus. Tatsächlich ist die Definition nur eine bestimmte Form der Erklärung . Jede Definition ist gleichzeitig eine Deklaration (mit wenigen Ausnahmen). Es gibt definierende Deklarationen (dh Definitionen) und nicht definierende Deklarationen. Außerdem wird normalerweise die Therm- Deklaration die ganze Zeit verwendet (auch wenn es sich um eine Definition handelt), mit Ausnahme der Kontexte, in denen die Unterscheidung zwischen den beiden kritisch ist.

13

Ich denke, es ist eine alte Gewohnheit, die aus "lokalen Deklarations" -Zeiten übrig geblieben ist. Und deshalb als Antwort auf Ihre Frage: Nein, ich glaube nicht, dass es einen guten Grund gibt. Ich mache es nie selbst.


4

Darüber habe ich in meiner Antwort auf eine Frage von Helium3 etwas gesagt .

Grundsätzlich sage ich, es ist eine visuelle Hilfe, um leicht zu sehen, was geändert wird.

if (a == 0) {
    struct whatever *myobject = 0;
    /* did `myobject` (the pointer) get assigned?
    ** or was it `*myobject` (the struct)? */
}

und

if (a == 0) {
    struct whatever *myobject;
    myobject = 0;
    /* `myobject` (the pointer) got assigned */
}

4

Die anderen Antworten sind ziemlich gut. In C gibt es eine Geschichte dazu. In C ++ gibt es den Unterschied zwischen einem Konstruktor und einem Zuweisungsoperator.

Ich bin überrascht, dass niemand den zusätzlichen Punkt erwähnt: Deklarationen von der Verwendung einer Variablen zu trennen, kann manchmal viel besser lesbar sein.

Wenn Sie Code lesen, sind die banaleren Artefakte, wie z. B. die Typen und Namen von Variablen, visuell nicht das, was Sie anspricht. Es sind die Aussagen , an denen Sie normalerweise am meisten interessiert sind, auf die Sie die meiste Zeit starren, und daher besteht die Tendenz, einen Blick auf den Rest zu werfen.

Wenn ich einige Typen, Namen und Zuordnungen auf engstem Raum habe, ist das ein bisschen Informationsüberflutung. Darüber hinaus bedeutet dies, dass in dem Bereich, über den ich normalerweise schaue, etwas Wichtiges vor sich geht.

Es mag etwas kontraproduktiv erscheinen, dies ist jedoch ein Beispiel dafür, wie Sie Ihre Quelle verbessern können, indem Sie mehr vertikalen Platz in Anspruch nehmen. Ich sehe das als eine Art Grund, warum Sie keine überfüllten Zeilen schreiben sollten, die verrückte Mengen an Zeigerarithmetik und -zuweisung in einem engen vertikalen Raum ausführen - nur weil die Sprache Sie mit solchen Dingen davonkommen lässt, heißt das nicht, dass Sie es tun sollten es die ganze Zeit. :-)


2

In C war dies die Standardpraxis, da Variablen zu Beginn der Funktion deklariert werden mussten, anders als in C ++, wo sie an einer beliebigen Stelle im Funktionskörper deklariert werden konnten, um danach verwendet zu werden. Zeiger wurden auf 0 oder NULL gesetzt, da nur sichergestellt wurde, dass der Zeiger auf keinen Müll zeigte. Ansonsten gibt es keinen signifikanten Vorteil, den ich mir vorstellen kann, was jeden dazu zwingt.


2

Vorteile für die Lokalisierung von Variablendefinitionen und deren sinnvolle Initialisierung:

  • Wenn Variablen gewohnheitsmäßig einen sinnvollen Wert zugewiesen bekommen, wenn sie zum ersten Mal im Code erscheinen (eine andere Perspektive auf dasselbe: Sie verzögern ihr Erscheinen, bis ein sinnvoller Wert verfügbar ist), besteht keine Chance, dass sie versehentlich mit einem sinnlosen oder nicht initialisierten Wert verwendet werden ( Dies kann leicht passieren, wenn eine Initialisierung aufgrund von bedingten Anweisungen, Kurzschlussauswertung, Ausnahmen usw. versehentlich umgangen wird.)

  • kann effizienter sein

    • Vermeidet Overheads beim Setzen des Anfangswerts (Standardkonstruktion oder Initialisierung auf einen Sentinel-Wert wie NULL)
    • operator= kann manchmal weniger effizient sein und ein temporäres Objekt erfordern
    • manchmal (insbesondere für Inline-Funktionen) kann der Optimierer einige / alle Ineffizienzen beseitigen

  • Das Minimieren des Bereichs von Variablen minimiert wiederum die durchschnittliche Anzahl von Variablen, die gleichzeitig im Bereich sind : dies

    • macht es einfacher, geistig verfolgen die Variablen im Umfang, die Ausführungsströme und Aussagen, die diese Variablen beeinflussen könnten, und den Import ihres Wertes
    • Zumindest für einige komplexe und undurchsichtige Objekte wird dadurch der Ressourcenverbrauch (Heap, Threads, gemeinsamer Speicher, Deskriptoren) des Programms verringert
  • manchmal prägnanter, da Sie den Variablennamen in einer Definition nicht wiederholen als in einer anfänglichen sinnvollen Zuweisung

  • Erforderlich für bestimmte Typen, z. B. Referenzen, und für den Zeitpunkt, zu dem das Objekt erstellt werden soll const

Argumente zum Gruppieren von Variablendefinitionen:

  • Manchmal ist es praktisch und / oder kurz , den Typ einer Reihe von Variablen herauszufiltern:

    the_same_type v1, v2, v3;

    (Wenn der Grund nur darin besteht, dass der Typname zu lang oder zu komplex ist, typedefkann a manchmal besser sein.)

  • Manchmal ist es wünschenswert, Variablen unabhängig von ihrer Verwendung zu gruppieren, um die Menge der Variablen (und Typen) hervorzuheben, die an einer Operation beteiligt sind:

    type v1;
    type v2; type v3;

    Dies unterstreicht die Gemeinsamkeit des Typs und macht es ein wenig einfacher, ihn zu ändern, während eine Variable pro Zeile beibehalten wird, die das Kopieren, Einfügen, //Kommentieren usw. erleichtert .

Wie so oft in der Programmierung, kann die eine Praxis in den meisten Situationen einen eindeutigen empirischen Nutzen haben, die andere Praxis kann in einigen Fällen wirklich überwältigend besser sein.


Ich wünschte, mehr Sprachen würden den Fall unterscheiden, in dem Code den Wert einer Variablen deklariert und festlegt, der an keiner anderen Stelle geschrieben werden würde, obwohl neue Variablen denselben Namen verwenden könnten eine andere], von denen, bei denen Code eine Variable erstellt, die an mehreren Stellen beschreibbar sein muss. Obwohl beide Anwendungsfälle auf die gleiche Weise ausgeführt werden, ist es sehr hilfreich zu wissen, wann sich Variablen ändern können, um Fehler aufzuspüren.
Supercat
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.