Pourquoi la plupart des programmeurs C nomment-ils des variables comme ceci:
int *myVariable;
plutôt que comme ça:
int* myVariable;
Les deux sont valides. Il me semble que l'astérisque fait partie du type et non du nom de la variable. Quelqu'un peut-il expliquer cette logique?
c
pointers
variables
naming-conventions
WBlasko
la source
la source
typedefs
, mais cela ajoutera une complexité inutile, à mon humble avis.Réponses:
Ils sont EXACTEMENT équivalents. Cependant, dans
int *myVariable, myVariable2;
Il semble évident que myVariable a le type int * , tandis 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 comme l'
myVariable2
a fait le type int .Par conséquent, le premier style de programmation est plus intuitif.
la source
int* someVar
des projets personnels. Cela a plus de sens.int[10] x
. Ce n'est tout simplement pas la syntaxe de C. La grammaire analyse explicitement comme:,int (*x)
et non comme(int *) x
, donc placer l'astérisque à gauche est tout simplement trompeur et basé sur une mauvaise compréhension de la syntaxe de déclaration C.int* myVariable; int myVariable2;
place.Si vous le regardez d'une autre manière, il
*myVariable
est de typeint
, ce qui a du sens.la source
myVariable
peut être NULL, auquel cas*myVariable
provoque une erreur de segmentation, mais il n'y a pas de type NULL.int x = 5; int *pointer = &x;
parce qu'il suggère que nous définissons l'int*pointer
à une certaine valeur, pas lepointer
lui - même.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 à réfléchir. Vous pouvez contourner complètement cela en déclarant une variable par ligne, ce qui n'est jamais ambigu:
int* varA; int varB;
L'équilibre entre un code clair et un code concis est difficile à trouver - une douzaine de lignes redondantes
int a;
ne sont pas bonnes non plus. Pourtant, je par défaut à une déclaration par ligne et je m'inquiète de combiner le code plus tard.la source
int *const a, b;
. Où le * "lie"? Le type dea
estint* const
, alors comment dire que le * appartient à la variable alors qu'il fait partie du type lui-même?Ce que personne n'a mentionné ici jusqu'à présent, c'est que cet astérisque est en fait "l' opérateur de déréférencement " en C.
*a = 10;
La ligne ci-dessus ne signifie pas que je veux attribuer
10
àa
, cela signifie que je veux attribuer10
à n'importe quel emplacement mémoire pointéa
. Et je n'ai jamais vu personne écrire* a = 10;
Avez-vous? L' opérateur de déréférence est donc presque toujours écrit sans espace. C'est probablement pour le distinguer d'une multiplication coupée sur plusieurs lignes:
Ce
*e
serait trompeur, n'est-ce pas?Ok, maintenant que signifie réellement la ligne suivante:
int *a;
La plupart des gens diraient:
Cela signifie que
a
c'est un pointeur vers uneint
valeur.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 à toutes les normes 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 déréférencée de
a
est de typeint
.Donc, en fait, l'astérisque dans cette déclaration peut également être vu comme un opérateur de déréférencement, ce qui explique également son emplacement. Et c'est
a
qu'un pointeur n'est pas vraiment déclaré du tout, c'est implicite par le 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 l'
*
opérateur:Et l'indirection n'est qu'une seule signification, il n'y a pas de signification supplémentaire pour déclarer un pointeur, il y a juste une indirection, ce que fait l'opération de déréférencement, elle effectue un accès indirect, donc aussi dans une instruction comme
int *a;
celle-ci est un accès indirect (*
signifie accès indirect) et donc la deuxième déclaration ci-dessus est beaucoup plus proche de la norme que la première.la source
int a, *b, (*c)();
comme quelque chose comme "déclarer les objets suivants commeint
: l'objeta
, l'objet pointé parb
et l'objet renvoyé par la fonction pointée parc
".*
inint *a;
n'est pas un opérateur, et ce n'est pas un déréférencementa
(qui n'est même pas encore défini)*
(il ne donne un sens qu'à l'expression totale, pas à l'*
intérieur de l'expression!). Il dit que "int a;" déclare un pointeur, ce qu'il fait, n'a jamais prétendu autrement, mais sans signification donnée*
, le lire comme * la valeur déréférencée dea
is an int est toujours totalement valide, car cela a la même signification factuelle. Rien, vraiment rien d'écrit dans 6.7.6.1 ne contredirait cette déclaration.int *a;
est une déclaration, pas une expression.a
n'est pas déréférencé parint *a;
.a
n'existe même pas encore au moment où le*
est en cours de traitement. Pensez-vous queint *a = NULL;
c'est un bogue parce qu'il déréférence un pointeur nul?Je vais sortir sur un membre ici et dire que il y a une réponse claire à cette question , à la fois pour les déclarations variables et pour les types de paramètres et de retour, qui est que l'astérisque devrait aller à côté du nom:
int *myVariable;
. Pour comprendre pourquoi, regardez comment vous déclarez d'autres types de symboles en C:int my_function(int arg);
pour une fonction;float my_array[3]
pour un tableau.Le modèle général, appelé déclaration suit l'utilisation , est que le type d'un symbole est divisé en la partie avant le nom et les parties autour du nom, et ces parties autour du nom imitent la syntaxe que vous utiliseriez pour obtenir un 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 les travaux avec des références, car la syntaxe au point où vous utilisez les références est identique à celle des types valeur, vous pouvez donc affirmer que C ++ adopte une approche différente de C. D'autre part, C ++ conserve la même comportement de C dans le cas des pointeurs, donc les références sont vraiment les plus étranges à cet égard.
la source
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 cela peut prêter à confusion lorsque vous mettez à la fois des variables et des pointeurs d'un type commun sur une seule ligne (ce qui est souvent découragé par les directives du projet, car diminue la lisibilité).
Je préfère déclarer les pointeurs avec leur signe correspondant à côté du nom du type, par exemple
int* pMyPointer;
la source
Un grand gourou a dit un jour: «Lisez-le à la manière du compilateur, vous devez».
http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835
Certes, c'était sur le sujet du placement des 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, cela facilitera la lecture de vos déclarations. Il évite également les maux de vue tels que:
int* a[10];
-- Éditer --
Pour expliquer exactement ce que je veux dire quand je dis que c'est analysé comme
int (*a)
, cela signifie que cela*
se lie plus étroitement àa
ce qu'il ne le fait àint
, de la même manière que dans l'expression4 + 3 * 7
3
se lie plus étroitement à7
cela en4
raison de la priorité plus élevée de*
.Avec des excuses pour l'art ascii, un synopsis de l'AST pour l'analyse
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 estDeclarator
, alors que vous devez monter tout en haut de l'arbreDeclaration
pour trouver un ancêtre commun qui implique leint
.la source
(int*) a
.int
dans ce cas. Étape 2. Lisez une déclaration, y compris les décorations de type.*a
dans ce cas. Étape 3 Lisez le caractère suivant. S'il s'agit d'une virgule, consommez-le et revenez à l'étape 2. Si le point-virgule s'arrête. Si quelque chose d'autre jette une erreur de syntaxe. ...int* a, b;
et d'obtenir une paire de pointeurs. Le point que je fais est que la*
liaison à la variable et est analysée avec elle, pas avec le type pour former le "type de base" de la déclaration. C'est aussi une des raisons pour lesquelles les typedefs ont été introduits pour permettre latypedef int *iptr;
iptr a, b;
création de quelques pointeurs. En utilisant un typedef, vous pouvez lier le*
auint
.Declaration-Specifier
avec les "décorations" duDeclarator
pour arriver au type final pour chaque variable. Cependant, il ne "déplace" pas les décorations vers le spécificateur de déclaration, sinonint a[10], b;
cela produirait des résultats complètement ridicules, il analyseint *a, b[10];
commeint
*a
,
b[10]
;
. Il n'y a pas d'autre moyen de le décrire qui ait du sens.int*a
est finalement lu par le compilateur comme: "a
has typeint*
". C'est ce que je veux dire avec mon commentaire initial.Les personnes qui préfèrent
int* x;
essaient de forcer leur code dans un monde fictif où le type est à gauche et l'identifiant (nom) est à droite.Je dis "fictif" parce que:
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 unint
et un tableau de pointeurs verschar
et renvoie unint
." En d'autres termes, la plupart des informations de type se trouvent à droite. Certaines personnes pensent que les déclarations de fonctions ne comptent pas parce qu'elles sont en quelque sorte «spéciales». OK, essayons une variable.void (*fn)(int)
signifiefn
est un pointeur vers une fonction qui prend unint
et ne renvoie rien.int a[10]
déclare «a» comme un tableau de 10int
s.pixel bitmap[height][width]
.De toute évidence, j'ai sélectionné des exemples avec beaucoup d'informations de type à droite pour faire valoir mon point. Il y a beaucoup de déclarations où la plupart - sinon la totalité - du type se trouve à gauche, comme
struct { int x; int y; } center
.Cette syntaxe de déclaration est née du désir de K&R d'avoir des déclarations reflétant 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 en spirale ou simplement la règle droite-gauche).
C est assez simple pour que de nombreux programmeurs C adoptent ce style et écrivent des déclarations simples comme
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
int* p
déclarations de style -style si vous extrayez une large bande de code C ++.Dans les deux langues, vous pouvez toujours avoir le type sur le côté gauche des déclarations de variables en (1) ne déclarant jamais plusieurs variables dans la même instruction, et (2) en utilisant
typedef
s (ou les déclarations d'alias, qui, ironiquement, mettent l'alias identificateurs à gauche des types). Par exemple:typedef int array_of_10_ints[10]; array_of_10_ints a;
la source
(*fn)
gardent le pointeur associé àfn
plutôt qu'au type de retour.Parce que cela a plus de sens lorsque vous avez des déclarations comme:
int *a, *b;
la source
int* a, b;
faitb
un pointeur versint
. Mais ils ne l'ont pas fait. Et avec raison. Dans le cadre de votre système proposé quel est le type deb
la déclaration suivante:int* a[10], b;
?Un grand nombre des arguments de cette rubrique sont simplement subjectifs et l'argument concernant «l'étoile se lie au nom de la variable» est naïf. Voici quelques arguments qui ne sont pas que des opinions:
Les qualificatifs de type de pointeur oublié
Formellement, l'étoile n'appartient ni au type ni au nom de la variable, elle fait partie de son propre élément grammatical nommé pointer . La syntaxe formelle C (ISO 9899: 2018) est:
Où les spécificateurs de déclaration contiennent le type (et le stockage) et la liste init-declarator-list contient le pointeur et le nom de la variable. Ce que nous voyons si nous disséquons davantage la syntaxe de cette liste de déclarateurs:
Lorsqu'un déclarateur est la déclaration complète, un déclarateur direct est l'identificateur (nom de la variable) et un pointeur est l'étoile suivie d'une liste de qualificatifs de type facultatifs appartenant au pointeur lui-même.
Ce qui rend incohérents les différents arguments de style concernant "l'étoile appartient à la variable", c'est qu'ils ont oublié ces qualificatifs de type pointeur.
int* const x
,int *const x
ouint*const x
?Considérez
int *const a, b;
, quels sont les types dea
etb
? Pas si évident que "l'étoile appartient à la variable" plus longtemps. Au contraire, on commencerait à se demander oùconst
appartient le.Vous pouvez certainement faire un argument valable selon lequel l'étoile appartient au qualificatif de type pointeur, mais pas beaucoup au-delà.
La liste des qualificatifs de type pour le pointeur peut poser des problèmes à ceux qui utilisent le
int *a
style. Ceux qui utilisent des pointeurs à l'intérieur d'untypedef
(ce que nous ne devrions pas, 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 compile proprement. Maintenant, vous pourriez penser que le code est rendu correct. Pas si! Ce code est accidentellement une fausse exactitude de const.
Le type de
foo
est en faitint*const*
- le pointeur le plus extérieur a été rendu en lecture seule, pas les données pointées. Donc, à l'intérieur de cette fonction, nous pouvons le 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! En pseudo-code, cette déclaration de paramètre doit être lue commeconst (bad_idea_t *) foo
et non comme(const bad_idea_t) *foo
. L'étoile appartient au type pointeur masqué dans ce cas - le type est un pointeur et un pointeur qualifié 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
typedef
et non le*
style.Concernant la déclaration de plusieurs variables sur une seule ligne
La déclaration de plusieurs variables sur une seule ligne est largement reconnue comme une mauvaise pratique 1) . CERT-C résume bien cela comme:
Il suffit de lire l'anglais, accepte alors de bon sens que la déclaration devrait être une déclaration.
Et peu importe si les variables sont des pointeurs ou non. Déclarer chaque variable sur une seule ligne rend le code plus clair dans presque tous les cas.
Donc, l'argument selon lequel le programmeur est confus
int* a, b
est mauvais. La racine du problème est l'utilisation de plusieurs déclarateurs, et non le placement du fichier*
. Quel que soit le style, vous devriez plutôt écrire ceci:int* a; // or int *a int b;
Un autre argument sain mais subjectif serait que, étant donné que
int* a
le type dea
est indiscutableint*
, l'étoile appartient donc au qualificatif de type.Mais au fond, ma conclusion est que bon nombre des arguments présentés ici sont simplement subjectifs et naïfs. Vous ne pouvez pas vraiment faire 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 .
la source
typedef int *bad_idea_t;
void func(const bad_idea_t bar);
Comme l'enseigne le grand prophète Dan Saks "Si vous placez toujours leconst
aussi loin que possible à droite, sans changer le sens sémantique", cela cesse complètement être un problème. Cela rend également vosconst
déclarations plus cohérentes à lire. "Tout ce qui est à droite du mot const est ce qui est const, tout à gauche est son type." Cela s'appliquera à tous les consts dans une déclaration. Essayez-le avecint const * * const x;
Pour déclarer plusieurs pointeurs sur une seule ligne, je préfère
int* a, * b;
lequel déclare plus intuitivement "a" comme un pointeur vers un entier, et ne mélange pas les styles en déclarant également "b". Comme quelqu'un l'a dit, je ne déclarerais pas deux types différents dans la même déclaration de toute façon.la source
Considérer
int *x = new int();
Cela ne se traduit pas par
int *x; *x = new int();
Cela se traduit en fait par
int *x; x = new int();
Cela rend la
int *x
notation quelque peu incohérente.la source
new
est un opérateur C ++. Sa question est étiquetée "C" et non "C ++".