Je me trompe toujours sur l'utilisation correcte de const int*
, const int * const
et int const *
. Existe-t-il un ensemble de règles définissant ce que vous pouvez et ne pouvez pas faire?
Je veux savoir tout ce qu'il y a à faire et à ne pas faire en termes de tâches, de passage aux fonctions, etc.
Lisez-le à l'envers (comme indiqué par Clockwise/Spiral Rule ):
int*
- pointeur sur intint const *
- pointeur sur const intint * const
- pointeur constant sur intint const * const
- pointeur constant sur const intMaintenant, la première const
peut être de chaque côté du type, ainsi:
const int *
== int const *
const int * const
== int const * const
Si vous voulez devenir vraiment fou, vous pouvez faire des choses comme celle-ci:
int **
- pointeur à pointeur à intint ** const
- un pointeur constant vers un pointeur vers un intint * const *
- un pointeur sur un const pointeur sur un intint const **
- un pointeur sur un pointeur sur un const intint * const * const
- un pointeur const à un pointeur const à un intEt pour être sûr de bien comprendre le sens de const
const int* foo;
int *const bar; //note, you actually need to set the pointer
//here because you can't change it later ;)
foo
est un pointeur de variable sur un entier constant. Cela vous permet de changer ce que vous pointez mais pas la valeur que vous pointez. Le plus souvent, cela se voit avec des chaînes de style C où vous avez un pointeur sur un const char
. Vous pouvez changer la chaîne sur laquelle vous pointez, mais vous ne pouvez pas modifier le contenu de ces chaînes. Ceci est important lorsque la chaîne elle-même est dans le segment de données d'un programme et ne doit pas être modifiée.
bar
est un pointeur constant ou fixe sur une valeur pouvant être modifiée. C'est comme une référence sans le sucre syntaxique supplémentaire. De ce fait, vous utiliserez généralement une référence dans laquelle vous utiliseriez un pointeur T* const
sauf si vous devez autoriser des pointeurs NULL
.
Pour ceux qui ne connaissent pas la règle Horloge/Spirale: Commencez par le nom de la variable, déplacez-vous dans le sens horaire (dans ce cas, reculez) jusqu'au prochain pointeur ou type . Répétez jusqu'à la fin de l'expression.
voici une démo:
Je pense que tout est déjà résolu ici, mais je veux juste ajouter que vous devriez vous méfier des typedef
s! Ce ne sont pas simplement des remplacements de texte.
Par exemple:
typedef char *ASTRING;
const ASTRING astring;
Le type de astring
est char * const
, pas const char *
. C’est une des raisons pour lesquelles j’ai toujours tendance à placer const
à droite du type, et jamais au début.
Comme à peu près tout le monde a fait remarquer:
Quelle est la différence entre const X* p
, X* const p
et const X* const p
?
Vous devez lire les déclarations de pointeur de droite à gauche.
const X* p
signifie "p pointe sur un X qui est const": l'objet X ne peut pas être modifié via p.
X* const p
signifie "p est un pointeur constant sur un X non constant": vous ne pouvez pas changer le pointeur p lui-même, mais vous pouvez changer l'objet X via p.
const X* const p
signifie "p est un pointeur const sur un X qui est const": vous ne pouvez pas modifier le pointeur p lui-même, ni l'objet X via p.
Référence constante:
Une référence à une variable (ici int) qui est constante. Nous transmettons principalement la variable comme référence, car les références sont de taille plus petite que la valeur réelle, mais il existe un effet secondaire et c’est qu’il s’agit d’un alias pour la variable réelle. Nous pouvons modifier accidentellement la variable principale via notre accès complet à l’alias. Nous la rendons donc constante pour éviter cet effet secondaire.
int var0 = 0;
const int &ptr1 = var0;
ptr1 = 8; // Error
var0 = 6; // OK
Pointeurs constants
Une fois qu'un pointeur constant pointe sur une variable, il ne peut pointer sur aucune autre variable.
int var1 = 1;
int var2 = 0;
int *const ptr2 = &var1;
ptr2 = &var2; // Error
Pointeur à constante
Un pointeur par lequel on ne peut pas changer la valeur d'une variable qu'elle désigne s'appelle un pointeur sur une constante.
int const * ptr3 = &var2;
*ptr3 = 4; // Error
Pointeur constant sur une constante
Un pointeur constant sur une constante est un pointeur qui ne peut ni changer l'adresse vers laquelle il pointe ni la valeur conservée à cette adresse.
int var3 = 0;
int var4 = 0;
const int * const ptr4 = &var3;
*ptr4 = 1; // Error
ptr4 = &var4; // Error
Cette question montre précisément pourquoi j'aime faire les choses comme je l'ai mentionné dans ma question est-ce que const après type est acceptable?
En bref, je trouve que le moyen le plus simple de se rappeler la règle est que le "const" va après la chose à laquelle il s'applique. Ainsi, dans votre question, "int const *" signifie que int est constant, alors que "int * const" voudrait dire que le pointeur est constant.
Si quelqu'un décide de le placer tout à fait au début (par exemple: "const int *"), à titre d'exception spéciale dans ce cas, il s'applique à la chose qui le suit.
Beaucoup de gens aiment utiliser cette exception spéciale parce qu'ils pensent que ça a l'air plus joli. Je n’aime pas ça, parce que c’est une exception et que cela confond les choses.
La règle générale est que le mot clé const
s'applique à ce qui le précède immédiatement. Exception, une const
de départ s'applique à ce qui suit.
const int*
est identique à int const*
et signifie "pointeur sur constante int".const int* const
est identique à int const* const
et signifie "pointeur constant sur constant int".Edit: Pour les choses à faire et à ne pas faire, si cette réponse n'est pas assez, pourriez-vous être plus précis sur ce que vous voulez?
Utilisation simple de ‘const’
L'utilisation la plus simple consiste à déclarer une constante nommée. Pour ce faire, on déclare une constante comme s’il s’agissait d’une variable mais en ajoutant ‘const’ devant elle. Il faut l'initialiser immédiatement dans le constructeur car, bien entendu, on ne peut pas définir la valeur plus tard, car cela la modifierait. Par exemple,
const int Constant1=96;
créera une constante entière, appelée «Constant1» sans imagination, avec la valeur 96.
De telles constantes sont utiles pour les paramètres utilisés dans le programme, mais ne doivent pas être modifiées après la compilation du programme. Il présente un avantage pour les programmeurs par rapport à la commande '#define' du préprocesseur C dans la mesure où il est compris et utilisé par le compilateur lui-même, et non simplement substitué dans le texte du programme par le préprocesseur avant d'atteindre le compilateur principal; .
Cela fonctionne aussi avec les pointeurs, mais il faut faire attention à où ‘const’ pour déterminer si le pointeur ou ce qu’il pointe est constant ou les deux. Par exemple,
const int * Constant2
déclare que Constant2 est un pointeur variable sur un entier constant et
int const * Constant2
est une syntaxe alternative qui fait la même chose, alors que
int * const Constant3
déclare que Constant3 est un pointeur constant sur un entier variable et
int const * const Constant4
déclare que Constant4 est un pointeur constant sur un entier constant. Fondamentalement, ‘const’ s’applique à tout ce qui se trouve à sa gauche immédiate (sauf s’il n’ya rien dans ce cas, auquel cas il s’applique à ce qui est son droit immédiat).
ref: http://duramecho.com/ComputerInformation/WhyHowCppConst.html
J'avais les mêmes doutes que vous jusqu'à ce que je tombe sur ce livre du gourou C++ Scott Meyers. Reportez-vous au troisième élément de ce livre où il explique en détail comment utiliser const
.
Il suffit de suivre ce conseil
const
apparaît à la gauche de l'astérisque, ce qui est indiqué est constantconst
apparaît à droite de l'astérisque, le pointeur lui-même est constantconst
apparaît des deux côtés, les deux sont constantsLa syntaxe de déclaration C et C++ a été décrite à plusieurs reprises comme une expérience ayant échoué par les concepteurs d'origine.
À la place, prenons name le type “pointeur sur Type
”; Je l'appellerai Ptr_
:
template< class Type >
using Ptr_ = Type*;
Maintenant, Ptr_<char>
est un pointeur sur char
.
Ptr_<const char>
est un pointeur sur const char
.
Et const Ptr_<const char>
est un pointeur const
sur const char
.
Là.
C'est simple mais délicat. Veuillez noter que nous pouvons échanger le qualificatif const
avec n'importe quel type de données (int
, char
, float
, etc.).
Voyons les exemples ci-dessous.
const int *p
==> *p
est en lecture seule [p
est un pointeur sur un entier constant]
int const *p
==> *p
est en lecture seule [p
est un pointeur sur un entier constant]
int *p const
==> Wrong Statement. Le compilateur renvoie une erreur de syntaxe.
int *const p
==> p
est en lecture seule [p
est un pointeur constant sur un entier] . Comme le pointeur p
est en lecture seule, la déclaration et la définition doivent être au même endroit.
const int *p const
==> Wrong Statement. Le compilateur renvoie une erreur de syntaxe.
const int const *p
==> *p
est en lecture seule
const int *const p1
==> *p
et p
sont en lecture seule [p
est un pointeur constant sur un entier constant]. Comme le pointeur p
est en lecture seule, la déclaration et la définition doivent être au même endroit.
int const *p const
==> Wrong Statement. Le compilateur renvoie une erreur de syntaxe.
int const int *p
==> Wrong Statement. Le compilateur renvoie une erreur de syntaxe.
int const const *p
==> *p
est en lecture seule et équivaut à int const *p
int const *const p
==> *p
et p
sont en lecture seule [p
est un pointeur constant sur un entier constant]. Comme le pointeur p
est en lecture seule, la déclaration et la définition doivent être au même endroit.
Il y a beaucoup d'autres points subtils entourant l'exactitude de const en C++. Je suppose que la question ici a simplement été à propos de C, mais je vais donner quelques exemples connexes puisque la balise est C++:
Vous transmettez souvent des arguments volumineux tels que des chaînes telles que TYPE const &
, ce qui empêche la modification ou la copie de l'objet. Exemple :
TYPE& TYPE::operator=(const TYPE &rhs) { ... return *this; }
Mais TYPE & const
n'a pas de sens car les références sont toujours const.
Vous devez toujours étiqueter les méthodes de classe qui ne modifient pas la classe sous la forme const
, sinon vous ne pourrez pas appeler la méthode à partir d'une référence TYPE const &
. Exemple :
bool TYPE::operator==(const TYPE &rhs) const { ... }
Il existe des situations courantes où la valeur de retour et la méthode doivent être const. Exemple :
const TYPE TYPE::operator+(const TYPE &rhs) const { ... }
En fait, les méthodes const ne doivent pas renvoyer de données de classe internes en tant que référence à non-const.
En conséquence, il est souvent nécessaire de créer une méthode const et une méthode non const en utilisant la surcharge const. Par exemple, si vous définissez T const& operator[] (unsigned i) const;
, vous voudrez probablement aussi la version non-const donnée par:
inline T& operator[] (unsigned i) { return const_cast<char&>( static_cast<const TYPE&>(*this)[](i) ); }
En clair, il n’existe pas de fonctions const en C, les fonctions non membres ne peuvent pas être constantes en C++, les méthodes const peuvent avoir des effets secondaires et le compilateur ne peut pas utiliser de fonctions const pour éviter les appels de fonction en double. En fait, même une simple référence int const &
pourrait indiquer que la valeur à laquelle elle fait référence est modifiée ailleurs.
Le const avec l'int de part et d'autre fera pointeur à constant int .
const int *ptr=&i;
ou
int const *ptr=&i;
const après '*' fera que pointeur constant sur int .
int *const ptr=&i;
Dans ce cas, tous ces éléments sont pointeur sur un entier constant , mais aucun d'entre eux n'est un pointeur constant.
const int *ptr1=&i, *ptr2=&j;
Dans ce cas, tous sont pointeur sur entier constant et ptr2 est constante pointeur sur entier constant . Mais ptr1 n'est pas un pointeur constant.
int const *ptr1=&i, *const ptr2=&j;
Cela concerne principalement la deuxième ligne: meilleures pratiques, attributions, paramètres de fonction, etc.
Pratique générale. Essayez de faire tout ce que vous pouvez const
. Autrement dit, commencez par tout remplacer par const
, puis supprimez exactement l'ensemble minimal de const
s nécessaire au fonctionnement du programme. Cela aidera énormément à atteindre la const-correct, et aidera à éviter que des bugs subtils ne soient introduits lorsque les gens essaient d'assigner des tâches qu'ils ne sont pas censés modifier.
Évitez const_cast <> comme la peste. Il existe un ou deux cas d'utilisation légitimes, mais ils sont très rares. Si vous essayez de changer un objet const
, vous ferez beaucoup mieux de trouver celui qui l'a déclaré const
dès le premier pas et de discuter de la question avec eux afin de parvenir à un consensus sur ce qui devrait se passer.
Ce qui mène très proprement à des missions. Vous pouvez assigner quelque chose seulement si c'est non-const. Si vous voulez assigner quelque chose qui est const, voir ci-dessus. Rappelez-vous que dans les déclarations int const *foo;
et int * const bar;
différentes choses sont const
- les autres réponses ici couvrent admirablement ce problème, je ne vais donc pas y revenir.
Paramètres de fonction:
Pass par valeur: par exemple void func(int param)
vous ne vous souciez pas d'une manière ou d'une autre sur le site appelant. L'argument peut être avancé qu'il existe des cas d'utilisation pour déclarer la fonction en tant que void func(int const param)
, mais cela n'a aucun effet sur l'appelant, uniquement sur la fonction elle-même, dans la mesure où la valeur transmise ne peut pas être modifiée par la fonction au cours de l'appel.
Passer par référence: par exemple void func(int ¶m)
Maintenant, cela fait une différence. Comme cela vient d'être déclaré, func
est autorisé à changer param
et tout site appelant doit être prêt à faire face aux conséquences. Le fait de changer la déclaration en void func(int const ¶m)
modifie le contrat et garantit que func
ne peut désormais plus changer param
, ce qui signifie que ce qui est transmis est ce qui va revenir. Comme d'autres l'ont noté, cela est très utile pour passer à bon marché un objet de grande taille que vous ne souhaitez pas modifier. Passer une référence coûte beaucoup moins cher que de passer un gros objet en valeur.
Passer par le pointeur: par exemple void func(int *param)
et void func(int const *param)
Ces deux noms sont à peu près synonymes avec leurs homologues de référence, à l'exception du fait que la fonction appelée doit maintenant vérifier nullptr
à moins qu'une autre garantie contractuelle assure func
qu'elle ne recevra jamais nullptr
dans param
.
Article d'opinion sur ce sujet. Prouver la correction dans un cas comme celui-ci est extrêmement difficile, il est trop facile de se tromper. Donc, ne prenez pas de risques et vérifiez toujours les paramètres du pointeur pour nullptr
. Vous vous épargnerez la douleur et la souffrance et il vous sera difficile de trouver des insectes à long terme. Et en ce qui concerne le coût du contrôle, il est très économique, et dans les cas où l'analyse statique intégrée au compilateur peut le gérer, l'optimiseur le supprimera quand même. Activez la génération de code temporel de liaison pour MSVC ou WOPR (je pense) pour GCC, et vous obtiendrez l’ensemble du programme, c’est-à-dire même dans les appels de fonction qui franchiront une frontière de module de code source.
En fin de compte, tout ce qui précède constitue un argument très solide pour toujours préférer les références aux pointeurs. Ils sont juste plus sûrs tout autour.
Pour moi, la position de const
, c’est-à-dire si elle apparaît à gauche ou à droite ou à gauche et à droite par rapport au *
m’aide à comprendre le sens réel.
Un const
à gauche de *
indique que l'objet pointé par le pointeur est un objet const
.
Une const
à DROITE de *
indique que le pointeur est un pointeur const
.
Le tableau ci-dessous est tiré de l’analyseur de cours de laboratoire de programmation standard C++ de Stanford CS106L.
const
est à gauche de *
, il fait référence à la valeur (peu importe que ce soit const int
ou int const
)const
est à droite de *
, il fait référence au pointeur lui-même.Un point important: const int *p
ne signifie pas que la valeur à laquelle vous vous référez est constante !!. Cela signifie que vous ne pouvez pas le changer via ce pointeur. La valeur elle-même peut être modifiée. Par exemple
int x = 5;
const int *p = &x;
x = 6; //legal
printf("%d", *p) // prints 6
*p = 7; //error
Ceci est destiné à être utilisé principalement dans les signatures de fonction, afin de garantir que la fonction ne puisse pas changer les arguments passés.
Juste pour être complet pour C suivant les autres explications, pas sûr pour C++.
x
int *p;
int const *p;
int * const p;
int const * const p;
int **pp;
int ** const pp;
int * const *pp;
int const **pp;
int * const * const pp;
int const ** const pp;
int const * const *pp;
int const * const * const pp;
// Example 1
int x;
x = 10;
int *p = NULL;
p = &x;
int **pp = NULL;
pp = &p;
printf("%d\n", **pp);
// Example 2
int x;
x = 10;
int *p = NULL;
p = &x;
int ** const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);
// Example 3
int x;
x = 10;
int * const p = &x; // Definition must happen during declaration
int * const *pp = NULL;
pp = &p;
printf("%d\n", **pp);
// Example 4
int const x = 10; // Definition must happen during declaration
int const * p = NULL;
p = &x;
int const **pp = NULL;
pp = &p;
printf("%d\n", **pp);
// Example 5
int x;
x = 10;
int * const p = &x; // Definition must happen during declaration
int * const * const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);
// Example 6
int const x = 10; // Definition must happen during declaration
int const *p = NULL;
p = &x;
int const ** const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);
// Example 7
int const x = 10; // Definition must happen during declaration
int const * const p = &x; // Definition must happen during declaration
int const * const *pp = NULL;
pp = &p;
printf("%d\n", **pp);
// Example 8
int const x = 10; // Definition must happen during declaration
int const * const p = &x; // Definition must happen during declaration
int const * const * const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);
Continuez, mais que l'humanité vous excommunie.
int x = 10;
int *p = &x;
int **pp = &p;
int ***ppp = &pp;
int ****pppp = &ppp;
printf("%d \n", ****pppp);