Perché dichiarare una variabile in una riga e assegnarla alla successiva?


101

Vedo spesso nel codice C e C ++ la seguente convenzione:

some_type val;
val = something;

some_type *ptr = NULL;
ptr = &something_else;

invece di

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

Inizialmente supponevo che fosse un'abitudine rimasta dai giorni in cui dovevi dichiarare tutte le variabili locali in cima all'ambito. Ma ho imparato a non respingere così in fretta le abitudini degli sviluppatori veterani. Quindi, c'è una buona ragione per dichiarare in una riga e assegnare in seguito?


12
+1 per "Ho imparato a non respingere così in fretta le abitudini degli sviluppatori veterani". Questa è una lezione saggia da imparare.
Wildcard il

Risposte:


92

C

In C89 tutte le dichiarazioni dovevano essere all'inizio di un ambito ( { ... }), ma questo requisito è stato abbandonato rapidamente (prima con le estensioni del compilatore e poi con lo standard).

C ++

Questi esempi non sono gli stessi. some_type val = something;chiama il costruttore della copia mentre val = something;chiama il costruttore predefinito e quindi la operator=funzione. Questa differenza è spesso critica.

abitudini

Alcune persone preferiscono prima dichiarare le variabili e successivamente definirle, nel caso in cui riformattino il loro codice in seguito con le dichiarazioni in un punto e la definizione in un altro.

Per quanto riguarda i puntatori, alcune persone hanno solo l'abitudine di inizializzare ogni puntatore NULLo nullptr, indipendentemente da ciò che fanno con quel puntatore.


1
Grande distinzione per C ++, grazie. Che dire della pianura C?
Jonathan Sterling,

13
Il fatto che MSVC non supporti ancora le dichiarazioni se non all'inizio di un blocco quando viene compilato in modalità C è per me fonte di infinita irritazione.
Michael Burr,

5
@Michael Burr: Questo perché MSVC non supporta affatto C99.
orlp

3
"some_type val = qualcosa; chiama il costruttore della copia": può chiamare il costruttore della copia, ma lo Standard consente al compilatore di eludere la costruzione di default di un tempiale, copiare la costruzione di val e la distruzione del temporaneo e costruire direttamente val usando un some_typecostruttore somethingcome unico argomento. Questo è un caso limite molto interessante e insolito in C ++ ... significa che c'è una presunzione sul significato semantico di queste operazioni.

2
@Aerovistae: per i tipi integrati sono uguali, ma non si può sempre dire lo stesso per i tipi definiti dall'utente.
orlp,

27

Hai taggato la tua domanda C e C ++ allo stesso tempo, mentre la risposta è significativamente diversa in queste lingue.

In primo luogo, la formulazione del titolo della domanda è errata (o, più precisamente, irrilevante per la domanda stessa). In entrambi i tuoi esempi la variabile viene dichiarata e definita contemporaneamente, in una riga. La differenza tra i tuoi esempi è che nella prima le variabili vengono lasciate non inizializzate o inizializzate con un valore fittizio e successivamente viene assegnato un valore significativo. Nel secondo esempio le variabili vengono inizializzate immediatamente.

In secondo luogo, in linguaggio C ++, come notato da @nightcracker nella sua risposta, questi due costrutti sono semanticamente diversi. Il primo si basa sull'inizializzazione, mentre il secondo sull'assegnazione. In C ++ queste operazioni sono sovraccaricabili e pertanto possono potenzialmente portare a risultati diversi (sebbene si possa notare che produrre sovraccarichi non equivalenti di inizializzazione e assegnazione non è una buona idea).

Nel linguaggio C standard originale (C89 / 90) è illegale dichiarare variabili nel mezzo del blocco, motivo per cui è possibile che vengano visualizzate variabili dichiarate non inizializzate (o inizializzate con valori fittizi) all'inizio del blocco e quindi assegnate significative valori in seguito, quando questi valori significativi diventano disponibili.

Nel linguaggio C99 è OK dichiarare variabili nel mezzo del blocco (proprio come in C ++), il che significa che il primo approccio è necessario solo in alcune situazioni specifiche quando l'inizializzatore non è noto al punto della dichiarazione. (Questo vale anche per C ++).


2
@Jonathan Sterling: ho letto i tuoi esempi. Probabilmente dovrai rispolverare la terminologia standard dei linguaggi C e C ++. In particolare, sui termini dichiarazione e definizione , che hanno significati specifici in queste lingue. Lo ripeterò di nuovo: in entrambi i tuoi esempi le variabili sono dichiarate e definite in una riga. In C / C ++ la riga dichiara e definiscesome_type val; immediatamente la variabile . Questo è ciò che intendevo nella mia risposta. val

1
Capisco cosa intendi lì. Hai decisamente ragione a dichiarare e definire essere piuttosto insignificanti nel modo in cui li ho usati. Spero che accetti le mie scuse per la povera formulazione e il commento sconsiderato.
Jonathan Sterling

1
Quindi, se il consenso è che "dichiarare" è la parola sbagliata, suggerirei che qualcuno con una migliore conoscenza dello standard di me modifichi la pagina di Wikibooks.
Jonathan Sterling

2
In qualsiasi altro contesto dichiarare sarebbe la parola giusta, ma dal momento che dichiarare è un concetto ben definito , con conseguenze, in C e C ++ non è possibile utilizzarlo come in altri contesti.
orlp

2
@ybungalobill: ti sbagli. La dichiarazione e la definizione in C / C ++ non si escludono a vicenda. In realtà, la definizione è solo una forma specifica di dichiarazione . Ogni definizione è una dichiarazione allo stesso tempo (con poche eccezioni). Esistono dichiarazioni definitive (es. Definizioni) e dichiarazioni non definitive. Inoltre, normalmente la dichiarazione therm viene utilizzata sempre (anche se è una definizione), ad eccezione dei contesti in cui la distinzione tra i due è critica.

13

Penso che sia una vecchia abitudine, rimanente dai tempi della "dichiarazione locale". E quindi come risposta alla tua domanda: No, non credo che ci sia una buona ragione. Non lo faccio mai da solo.


4

Ho detto qualcosa al riguardo nella mia risposta a una domanda di Helium3 .

Fondamentalmente, dico che è un aiuto visivo per vedere facilmente cosa è cambiato.

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

e

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

4

Le altre risposte sono abbastanza buone. C'è un po 'di storia in questo in C. In C ++ c'è la differenza tra un costruttore e un operatore di assegnazione.

Sono sorpreso che nessuno menzioni il punto aggiuntivo: mantenere le dichiarazioni separate dall'uso di una variabile a volte può essere molto più leggibile.

Visivamente parlando, quando si legge il codice, i manufatti più banali, come i tipi e i nomi delle variabili, non sono ciò che ti salta fuori. Sono le dichiarazioni che di solito ti interessano di più, trascorrono la maggior parte del tempo a fissare, e quindi c'è la tendenza a dare un'occhiata al resto.

Se ho alcuni tipi, nomi e compiti in corso nello stesso spazio ristretto, è un po 'di sovraccarico di informazioni. Inoltre, significa che sta succedendo qualcosa di importante nello spazio su cui di solito guardo.

Può sembrare un po 'controintuitivo dirlo, ma questa è un'istanza in cui rendere la tua sorgente occupare più spazio verticale può renderlo migliore. Vedo che è simile al motivo per cui non dovresti scrivere righe piene di marmellata che fanno impazzite quantità di aritmetica e assegnazione del puntatore in uno spazio verticale stretto - solo perché il linguaggio ti consente di cavartela con queste cose non significa che dovresti farlo tutto il tempo. :-)


2

In C, questa era la pratica standard perché le variabili dovevano essere dichiarate all'inizio della funzione, a differenza di C ++, dove poteva essere dichiarata in qualsiasi punto del corpo della funzione per essere utilizzata in seguito. I puntatori erano impostati su 0 o NULL, perché si assicurava che il puntatore non indicasse immondizia. Altrimenti, non mi viene in mente alcun vantaggio significativo, il che obbliga chiunque a fare così.


2

Pro per la localizzazione delle definizioni delle variabili e la loro inizializzazione significativa:

  • se alle variabili viene assegnato abitualmente un valore significativo quando compaiono per la prima volta nel codice (un'altra prospettiva sulla stessa cosa: si ritarda il loro aspetto fino a quando un valore significativo è disponibile), allora non c'è alcuna possibilità che vengano accidentalmente utilizzate con un valore insignificante o non inizializzato ( che può facilmente avvenire se un'inizializzazione viene accidentalmente esclusa a causa di dichiarazioni condizionali, valutazione del corto circuito, eccezioni ecc.)

  • può essere più efficiente

    • evita le spese generali di impostazione del valore iniziale (costruzione predefinita o inizializzazione su un valore sentinella come NULL)
    • operator= a volte può essere meno efficiente e richiedere un oggetto temporaneo
    • a volte (specialmente per le funzioni in linea) l'ottimizzatore può rimuovere alcune / tutte le inefficienze

  • minimizzare l'ambito delle variabili a sua volta minimizza il numero medio di variabili contemporaneamente nell'ambito : questo

    • lo rende facile per monitorare mentalmente le variabili in campo di applicazione, i flussi di esecuzione e dichiarazioni che potrebbero influenzare tali variabili, e l'importazione del loro valore
    • almeno per alcuni oggetti complessi e opachi, questo riduce l'utilizzo delle risorse (heap, thread, memoria condivisa, descrittori) del programma
  • a volte più conciso poiché non stai ripetendo il nome della variabile in una definizione rispetto a un incarico iniziale significativo

  • necessario per alcuni tipi come riferimenti e quando si desidera che l'oggetto sia const

Argomenti per raggruppare le definizioni delle variabili:

  • a volte è conveniente e / o conciso scomporre il tipo di un numero di variabili:

    the_same_type v1, v2, v3;

    (se il motivo è solo che il nome del tipo è troppo lungo o complesso, a typedefvolte può essere migliore)

  • a volte è desiderabile raggruppare le variabili indipendentemente dal loro utilizzo per enfatizzare l'insieme di variabili (e tipi) coinvolti in alcune operazioni:

    type v1;
    type v2; type v3;

    Ciò enfatizza la comunanza del tipo e rende un po 'più semplice cambiarli, pur restando fedele a una variabile per riga che facilita il copia-incolla, i //commenti , ecc.

Come spesso accade nella programmazione, mentre ci può essere un chiaro beneficio empirico in una pratica nella maggior parte delle situazioni, l'altra pratica può davvero essere straordinariamente migliore in alcuni casi.


Vorrei che più lingue distinguessero il caso in cui il codice dichiara e imposta il valore di una variabile che non verrebbe mai scritto altrove, anche se le nuove variabili potrebbero usare lo stesso nome [cioè dove il comportamento sarebbe lo stesso se le istruzioni successive usassero la stessa variabile o diverso], da quelli in cui il codice crea una variabile che deve essere scrivibile in più punti. Mentre entrambi i casi d'uso verranno eseguiti allo stesso modo, sapere quando le variabili possono cambiare è molto utile quando si cerca di rintracciare i bug.
supercat,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.