web-dev-qa-db-fra.com

Pourquoi l'astérisque se trouve-t-il avant le nom de la variable plutôt qu'après le type?

Pourquoi la plupart des programmeurs C nomment-ils les variables comme ceci:

int *myVariable;

plutôt que comme ceci:

int* myVariable;

Les deux sont valables. Il me semble que l'astérisque fait partie du type, pas du nom de la variable. Quelqu'un peut-il expliquer cette logique?

170
WBlasko

Ils sont EXACTEMENT équivalents. Cependant, dans

int *myVariable, myVariable2;

Il semble évident que myVariable a le type int *, alors que myVariable2 a le type int. Dans

int* myVariable, myVariable2;

il peut sembler évident que les deux sont de type int *, mais ce n'est pas correct car myVariable2 a le type int.

Par conséquent, le premier style de programmation est plus intuitif.

227
luiscubal

Si vous le regardez autrement, *myVariable est de type int, ce qui a du sens.

111
biozinc

Parce que le * dans cette ligne se lie plus étroitement à la variable qu'au type:

int* varA, varB; // This is misleading

Comme @Lundin le souligne ci-dessous, const ajoute encore plus de subtilités à prendre en compte. Vous pouvez entièrement éviter cela en déclarant une variable par ligne, ce qui n’est jamais ambigu:

int* varA;
int varB;

Il est difficile de trouver un équilibre entre code clair et code concis - une douzaine de lignes redondantes de int a; n'est pas bon non plus. Néanmoins, je passe par défaut à une déclaration par ligne et je m'inquiète de la combinaison de code ultérieurement.

39
ojrac

Quelque chose que personne n’a mentionné jusqu’ici est que cet astérisque est en fait l’opérateur " de déréférence " en C.

*a = 10;

La ligne ci-dessus ne signifie pas que je veux attribuer 10 à a, cela signifie que je veux assigner 10 vers n'importe quel emplacement mémoire a sur lequel pointe. Et je n'ai jamais vu personne écrire

* a = 10;

avez-vous? Ainsi, l'opérateur de déréférence est presque toujours écrit sans espace. C'est probablement pour le distinguer d'une multiplication divisée en plusieurs lignes:

x = a * b * c * d
  * e * f * g;

Ici *e serait trompeur, n'est-ce pas?

Bon, maintenant que signifie la ligne suivante:

int *a;

La plupart des gens diraient:

Cela signifie que a est un pointeur sur une valeur int.

C'est techniquement correct, la plupart des gens aiment le voir/le lire de cette façon et c'est ainsi que les normes C modernes le définiraient (notez que le langage C lui-même est antérieur à tous les standards ANSI et ISO). Mais ce n'est pas la seule façon de voir les choses. Vous pouvez également lire cette ligne comme suit:

La valeur référencée de a est de type int.

En fait, l'astérisque dans cette déclaration peut également être considéré comme un opérateur de déréférence, ce qui explique également son emplacement. Et que a est un pointeur qui n’est pas vraiment déclaré, c’est implicite du fait que la seule chose que vous pouvez réellement déréférencer est un pointeur.

La norme C ne définit que deux significations pour le * _ opérateur:

  • opérateur d'indirection
  • opérateur de multiplication

Et indirection n’est qu’un sens unique, il n’ya pas de sens supplémentaire pour déclarer un pointeur, il existe simplement un indirection, comme le fait l’opération de déréférencement, elle effectue un accès indirect, aussi dans une instruction comme int *a; ceci est un indirect accès (* signifie accès indirect) et la seconde déclaration ci-dessus est donc beaucoup plus proche de la norme que la première.

30
Mecki

Je vais aller sur une branche ici et dire que il y a une réponse directe à cette question , à la fois pour les déclarations de variable et pour paramètre et retour types, c'est-à-dire que l'astérisque doit être placé à côté du nom: int *myVariable;. Pour comprendre pourquoi, regardez comment vous déclarez d'autres types de symbole en C:

int my_function(int arg); pour une fonction;

float my_array[3] Pour un tableau.

Le schéma général, appelé la déclaration suit use, consiste en ce que le type d'un symbole est scindé en une partie précédant le nom, et les parties autour de le nom, et ces parties autour du nom imitent la syntaxe que vous utiliseriez pour obtenir une valeur du type à gauche:

int a_return_value = my_function(729);

float an_element = my_array[2];

et: int copy_of_value = *myVariable;.

C++ jette une clé dans le travail avec les références, car la syntaxe au point où vous utilisez les références est identique à celle des types de valeur, vous pouvez donc affirmer que C++ adopte une approche différente de C. Par contre, C++ conserve le même comportement de C dans le cas des pointeurs, les références sont donc vraiment les plus étranges à cet égard.

17
Injektilo

C'est juste une question de préférence.

Lorsque vous lisez le code, la distinction entre les variables et les pointeurs est plus facile dans le second cas, mais elle peut prêter à confusion lorsque vous insérez des variables et des pointeurs de type commun sur une seule ligne (ce qui est souvent découragé par les directives de projet, car diminue la lisibilité).

Je préfère déclarer les pointeurs avec le signe correspondant à côté du nom du type, par exemple.

int* pMyPointer;
11
macbirdie

Un grand gourou a dit un jour "Lis-le à la manière du compilateur, tu le dois."

http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835

Certes, cela concernait le placement de const, mais la même règle s’applique ici.

Le compilateur le lit comme suit:

int (*a);

pas aussi:

(int*) a;

Si vous prenez l’habitude de placer l’étoile à côté de la variable, vos déclarations seront plus lisibles. Il évite également les maux de tête tels que:

int* a[10];

-- Modifier --

Pour expliquer exactement ce que je veux dire quand je dis que c'est analysé comme int (*a), cela signifie que * Se lie plus étroitement à a qu'à int, de la même manière que dans l'expression 4 + 3 * 73 se lie plus étroitement à 7 qu'à 4 en raison de la priorité plus élevée de *.

Avec des excuses pour l'art ascii, un résumé du livre A.S.T. pour analyser int *a ressemble à peu près à ceci:

      Declaration
      /         \
     /           \
Declaration-      Init-
Secifiers       Declarator-
    |             List
    |               |
    |              ...
  "int"             |
                Declarator
                /       \
               /        ...
           Pointer        \
              |        Identifier
              |            |
             "*"           |
                          "a"

Comme il est clairement montré, * Se lie plus étroitement à a puisque leur ancêtre commun est Declarator, alors que vous devez aller jusqu'au bout de l'arbre pour que Declaration pour trouver un ancêtre commun impliquant le int.

7
dgnuff

Dans K & R, ils placent l'opérateur de pointeur à côté du nom de la variable plutôt que du type. Personnellement, j'estime que ce livre est l'ultime autorité/bible en C. Venant également d’un arrière-plan Objective-C, je suis la convention * name.

6
The_Androctonus

Parce que c'est plus logique quand vous avez des déclarations comme:

int *a, *b;
3
Greg Rogers

Pour déclarer plusieurs pointeurs sur une ligne, je préfère int* a, * b; qui déclare plus intuitivement "a" en tant que pointeur sur un entier et ne mélange pas les styles tout en déclarant "b". Comme quelqu'un l'a dit, de toute façon, je ne déclarerais pas deux types différents dans la même déclaration.

2
heyitsluke

J'ai déjà répondu à une question similaire dans CP, et, parce que personne ne l'a mentionné, je dois également souligner ici que C est un langage au format libre , quel que soit le style choisi, l’analyseur peut faire une distinction entre chaque jeton. Cette particularité de C conduit à un type de concours très spécial appelé concours d'obfuscation de C .

1
Frankie_C

Les personnes qui préfèrent int* x; Essaient de forcer leur code dans un monde fictif où le type est à gauche et l'identificateur (nom) à droite.

Je dis "fictif" parce que:

En C et C++, dans le cas général, l'identifiant déclaré est entouré des informations de type.

Cela peut sembler fou, mais vous savez que c'est vrai. Voici quelques exemples:

  • int main(int argc, char *argv[]) signifie "main est une fonction qui prend un int et un tableau de pointeurs vers char et retourne un int." En d'autres termes, la plupart du type d'information est à droite. Certaines personnes pensent que les déclarations de fonction ne comptent pas parce qu'elles sont en quelque sorte "spéciales". OK, essayons une variable.

  • void (*fn)(int) signifie que fn est un pointeur sur une fonction qui prend un int et ne renvoie rien.

  • int a[10] Déclare 'a' sous la forme d'un tableau de 10 ints.

  • pixel bitmap[height][width].

  • Clairement, j'ai choisi des exemples qui ont beaucoup d'informations de type sur le droit de faire valoir mon point de vue. Il y a beaucoup de déclarations où la plupart - sinon la totalité - du type est à gauche, comme struct { int x; int y; } center.

Cette syntaxe de déclaration est née du souhait de K & R de faire en sorte que les déclarations reflètent l'usage. La lecture de déclarations simples est intuitive, et la lecture de déclarations plus complexes peut être maîtrisée en apprenant la règle droite-gauche-droite (appelez parfois la règle spirale ou simplement la règle droite-gauche).

C est assez simple pour que beaucoup de programmeurs C adoptent ce style et écrivent des déclarations simples sous la forme int *p.

En C++, la syntaxe est devenue un peu plus complexe (avec des classes, des références, des modèles, des classes enum) et, en réaction à cette complexité, vous verrez plus d'efforts pour séparer le type de l'identifiant dans de nombreuses déclarations. En d'autres termes, vous pourriez voir plus de déclarations de style int* p Si vous extrayez une grande bande de code C++.

Dans les deux langues, can a toujours le type à gauche de variable déclarations de (1) ne jamais déclarer plusieurs variables dans la même instruction, et (2) utiliser de typedefs (ou des déclarations d'alias qui, paradoxalement, placent les identificateurs d'alias à gauche des types). Par exemple:

typedef int array_of_10_ints[10];
array_of_10_ints a;
1
Adrian McCarthy

De nombreux arguments de cette rubrique sont purement subjectifs et l'argument relatif à "l'étoile se lie au nom de la variable" est naïf. Voici quelques arguments qui ne sont pas que des opinions:


Les qualificateurs de type de pointeur oubliés

Formellement, "l'étoile" n'appartient ni au type ni au nom de variable, elle fait partie de son propre élément grammatical nommé pointeur. La syntaxe C formelle (ISO 9899: 2018) est la suivante:

(6.7) déclaration:
déclaration-spécificateurs= init-declarator-listopt ;

déclaration-specifiers contient le type (et la mémoire), et init-declarator-list contient le pointeur et le nom de la variable. Ce que nous voyons si nous disséquions plus avant cette syntaxe de liste de déclarants:

(6.7.6) déclarateur:
pointeuropt déclarant direct
...
(6.7.6) pointeur:
* type-qualifier-listopt
* type-qualifier-listopt  pointeur

Lorsqu'un déclarant est la déclaration complète, un déclarant direct est l'identifiant (nom de la variable) et un pointeur est l'étoile, suivie d'une liste de qualificateurs de type facultatifs appartenant au pointeur lui-même.

Ce qui rend incohérents les différents arguments de style sur "l'étoile appartient à la variable", c'est qu'ils ont oublié ces qualificateurs de type pointeur. int* const x, int *const x Ou int*const x?

Considérons int *const a, b;, Quels sont les types de a et b? Pas si évident que "l'étoile appartient à la variable" plus longtemps. Au contraire, on commencerait à se demander à quoi le const appartient.

Vous pouvez certainement argumenter que l'étoile appartient au qualificatif de type pointeur, mais pas beaucoup plus loin.

La liste de qualificateurs de type pour le pointeur peut poser des problèmes à ceux qui utilisent le style int *a. Ceux qui utilisent des pointeurs dans un typedef (ce qui ne devrait pas être une très mauvaise pratique!) Et pensent que "l'étoile appartient au nom de la variable" ont tendance à écrire ce bogue très subtil:

    /*** bad code, don't do this ***/
    typedef int *bad_idea_t; 
    ...
    void func (const bad_idea_t *foo);

Cela se compile proprement. Vous pouvez maintenant penser que le code est rendu const correct. Pas si! Ce code est accidentellement une exactitude sim simulée.

Le type de foo est en réalité int*const* - le pointeur le plus externe a été créé en lecture seule, et non le pointeur de données. Donc, à l'intérieur de cette fonction, nous pouvons faire **foo = n; Et cela changera la valeur de la variable dans l'appelant.

En effet, dans l'expression const bad_idea_t *foo, Le * N'appartient pas au nom de la variable ici! Dans le pseudo-code, cette déclaration de paramètre doit être lue comme const (bad_idea_t *) foo et pas comme (const bad_idea_t) *foo. L'étoile appartient au type de pointeur caché dans ce cas - le type est un pointeur et un pointeur qualifié de const est écrit comme *const.

Mais alors, la racine du problème dans l'exemple ci-dessus est la pratique de cacher des pointeurs derrière un style typedef et non le style *.


Concernant la déclaration de plusieurs variables sur une seule ligne

Déclarer plusieurs variables sur une seule ligne est largement reconnu comme une mauvaise pratique1). CERT-C le résume bien comme suit:

DCL04-C. Ne déclarez pas plus d'une variable par déclaration

En lisant simplement l'anglais, le bon sens convient que a déclaration doit être un déclaration.

Et peu importe si les variables sont des pointeurs ou non. La déclaration de chaque variable sur une seule ligne rend le code plus clair dans presque tous les cas.

Donc, l'argument voulant que le programmeur soit confus au sujet de int* a, b Est mauvais. La racine du problème est l'utilisation de plusieurs déclarateurs, pas le placement du *. Indépendamment du style, vous devriez plutôt écrire ceci:

    int* a; // or int *a
    int b;

Un autre argument valable mais subjectif serait que, étant donné int* a, Le type de a est sans question int* Et que l'étoile appartient donc au qualificateur de type.

Mais ma conclusion est que beaucoup des arguments présentés ici ne sont que subjectifs et naïfs. Vous ne pouvez pas vraiment faire valoir un argument valable pour l’un ou l’autre style - c’est vraiment une question de préférence personnelle subjective.


1) CERT-C DCL04-C .

0
Lundin