web-dev-qa-db-fra.com

Pourquoi déclarer une variable sur une ligne et l'affecter à la suivante?

Je vois souvent en code C et C++ la convention suivante:

some_type val;
val = something;

some_type *ptr = NULL;
ptr = &something_else;

au lieu de

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

J'ai d'abord supposé que c'était une habitude qui restait du temps où vous deviez déclarer toutes les variables locales en haut de la portée. Mais j'ai appris à ne pas rejeter aussi rapidement les habitudes des développeurs chevronnés. Alors, y a-t-il une bonne raison de déclarer en une seule ligne, et d'affecter ensuite?

102

C

En C89, toutes les déclarations devaient être être au début d'une étendue ({ ... }), mais cette exigence a été abandonnée rapidement (d'abord avec les extensions du compilateur et plus tard avec la norme).

C++

Ces exemples ne sont pas les mêmes. some_type val = something; appelle le constructeur de copie tandis que val = something; appelle le constructeur par défaut, puis le operator= une fonction. Cette différence est souvent critique.

Des habitudes

Certaines personnes préfèrent d'abord déclarer des variables et les définir plus tard, dans le cas où elles reformateraient leur code plus tard avec les déclarations à un endroit et la définition à un autre.

À propos des pointeurs, certaines personnes ont l'habitude d'initialiser chaque pointeur sur NULL ou nullptr, peu importe ce qu'ils font avec ce pointeur.

92
orlp

Vous avez tagué votre question C et C++ en même temps, tandis que la réponse est sensiblement différente dans ces langages.

Premièrement, le libellé du titre de votre question est incorrect (ou, plus précisément, sans rapport avec la question elle-même). Dans vos deux exemples, la variable est déclarée et définie simultanément, sur une seule ligne. La différence entre vos exemples est que dans le premier, les variables sont laissées non initialisées ou initialisées avec une valeur fictive et ensuite on lui attribue une valeur significative plus tard. Dans le deuxième exemple, les variables sont initialisées immédiatement.

Deuxièmement, en langage C++, comme @nightcracker l'a noté dans sa réponse, ces deux constructions sont sémantiquement différentes. Le premier repose sur l'initialisation tandis que le second - sur l'affectation. En C++, ces opérations sont surchargeables et peuvent donc potentiellement conduire à des résultats différents (bien que l'on puisse noter que produire des surcharges non équivalentes d'initialisation et d'affectation n'est pas une bonne idée).

Dans le langage C standard d'origine (C89/90), il est illégal de déclarer des variables au milieu du bloc, c'est pourquoi vous pouvez voir des variables déclarées non initialisées (ou initialisées avec des valeurs fictives) au début du bloc, puis affectées de manière significative valeurs plus tard, lorsque ces valeurs significatives deviennent disponibles.

En langage C99, il est OK de déclarer des variables au milieu du bloc (comme en C++), ce qui signifie que la première approche n'est nécessaire que dans certaines situations spécifiques lorsque l'initialiseur n'est pas connu au moment de la déclaration. (Cela s'applique également au C++).

27
AnT

Je pense que c'est une vieille habitude, un vestige de l'époque de la "déclaration locale". Et donc comme réponse à votre question: Non, je ne pense pas qu'il y ait une bonne raison. Je ne le fais jamais moi-même.

13
CornflakesDK

J'ai dit quelque chose à ce sujet dans ma réponse à ne question d'Helium .

Fondamentalement, je dis que c'est une aide visuelle pour voir facilement ce qui a changé.

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

et

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

Les autres réponses sont plutôt bonnes. Il y a un peu d'histoire à ce sujet en C. En C++, il y a la différence entre un constructeur et un opérateur d'affectation.

Je suis surpris que personne ne mentionne le point supplémentaire: garder les déclarations distinctes de l'utilisation d'une variable peut parfois être beaucoup plus lisible.

Visuellement, lors de la lecture de code, les artefacts les plus banals, tels que les types et les noms de variables, ne sont pas ce qui vous saute aux yeux. C'est le déclarations que vous êtes généralement le plus intéressé, passez le plus de temps à regarder, et donc il y a une tendance à regarder le reste.

Si j'ai certains types, noms et affectations dans le même espace restreint, c'est un peu de surcharge d'informations. De plus, cela signifie que quelque chose d'important se passe dans l'espace que je regarde habituellement.

Cela peut sembler un peu contre-intuitif à dire, mais c'est un cas où faire en sorte que votre source occupe plus d'espace vertical peut l'améliorer. Je vois cela comme une raison pour laquelle vous ne devriez pas écrire des lignes pleines à craquer qui font des quantités folles d'arithmétique et d'affectation de pointeurs dans un espace vertical serré - ce n'est pas parce que le langage vous permet de vous en sortir que vous devriez faire tout le temps. :-)

4
asveikau

En C, c'était la pratique standard car les variables devaient être déclarées au début de la fonction, contrairement à C++, où elle pouvait être déclarée n'importe où dans le corps de la fonction pour être utilisée par la suite. Les pointeurs ont été définis sur 0 ou NULL, car il s'est juste assuré que le pointeur ne pointait sur aucune ordure. Sinon, il n'y a aucun avantage significatif auquel je puisse penser, qui oblige quiconque à faire comme ça.

2
Vite Falcon

Avantages pour localiser les définitions de variables et leur initialisation significative:

  • si les variables se voient habituellement attribuer une valeur significative lors de leur première apparition dans le code (une autre perspective sur la même chose: vous retardez leur apparition jusqu'à ce qu'une valeur significative soit disponible) alors il y a aucune chance de les utiliser accidentellement avec valeur vide de sens ou non initialisée (ce qui peut facilement se produire si une initialisation est accidentellement contournée en raison d'instructions conditionnelles, d'une évaluation de court-circuit, d'exceptions, etc.)

  • peut être plus efficace

    • évite les frais généraux de définition de la valeur initiale (construction par défaut ou initialisation à une valeur sentinelle comme NULL)
    • operator= peut parfois être moins efficace et nécessiter un objet temporaire
    • parfois (en particulier pour les fonctions en ligne), l'optimiseur peut supprimer certaines/toutes les inefficacités

  • minimiser la portée des variables à son tour minimise le nombre moyen de variables simultanément dans la portée: ceci

    • rend plus facile à suivre mentalement les variables dans la portée, les flux d'exécution et les instructions qui pourraient affecter ces variables, et l'importation de leur valeur
    • au moins pour certains objets complexes et opaques, cela réduit l'utilisation des ressources (tas, threads, mémoire partagée, descripteurs) du programme
  • parfois plus concis car vous ne répétez pas le nom de la variable dans une définition puis dans une affectation significative initiale

  • nécessaire pour certains types tels que les références et lorsque vous voulez que l'objet soit const

Arguments pour regrouper les définitions de variables:

  • parfois c'est pratique et/ou concis pour factoriser le type d'un certain nombre de variables:

    the_same_type v1, v2, v3;

    (si la raison est simplement que le nom du type est trop long ou complexe, un typedef peut parfois être meilleur)

  • il est parfois souhaitable de regrouper les variables indépendamment de leur utilisation pour mettre en évidence l'ensemble des variables (et types) impliqués dans certaines opérations:

    type v1;
    type v2;type v3;

    Cela souligne la similitude de type et facilite un peu leur changement, tout en restant fidèle à une variable par ligne qui facilite le copier-coller, // commentant etc.

Comme c'est souvent le cas dans la programmation, bien qu'il puisse y avoir un avantage empirique clair pour une pratique dans la plupart des situations, l'autre pratique peut vraiment être nettement meilleure dans quelques cas.

2
Tony