web-dev-qa-db-fra.com

Quelle est la difference entre const int *, const int * const et int const *?

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.

1126
ultraman

Lisez-le à l'envers (comme indiqué par Clockwise/Spiral Rule ):

  • int* - pointeur sur int
  • int const * - pointeur sur const int
  • int * const - pointeur constant sur int
  • int const * const - pointeur constant sur const int

Maintenant, 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 à int
  • int ** const - un pointeur constant vers un pointeur vers un int
  • int * const * - un pointeur sur un const pointeur sur un int
  • int const ** - un pointeur sur un pointeur sur un const int
  • int * const * const - un pointeur const à un pointeur const à un int
  • ...

Et 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.

1920
Matt Price

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:

pointer to int

const pointer to int const

pointer to int const

pointer to const int

const pointer to int

274
Shijing Lv

Je pense que tout est déjà résolu ici, mais je veux juste ajouter que vous devriez vous méfier des typedefs! 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.

130
Kaz Dragon

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.

47
luke
  1. 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
    
  2. 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
    
  3. 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
    
  4. 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
    
41
Behrooz Tabesh

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.

16
T.E.D.

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?

15
AProgrammer

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

14
ufukgun

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

  1. Si le mot const apparaît à la gauche de l'astérisque, ce qui est indiqué est constant
  2. Si le mot const apparaît à droite de l'astérisque, le pointeur lui-même est constant
  3. Si const apparaît des deux côtés, les deux sont constants
7
rgk

La 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à.

 enter image description here

5

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.

5
Abhijit Sahu

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.

5
Jeff Burdges

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;
2
Hunter

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 consts 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 &param) 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 &param) 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.

1
dgnuff

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.

  1. Un const à gauche de * indique que l'objet pointé par le pointeur est un objet const.

  2. 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.

 enter image description here

1
srivatsahc
  • si const est à gauche de *, il fait référence à la valeur (peu importe que ce soit const int ou int const)
  • si const est à droite de *, il fait référence au pointeur lui-même.
  • il peut être les deux en même temps

Un point important: const int *pne 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.

0
blue_note

Juste pour être complet pour C suivant les autres explications, pas sûr pour C++.

  • pp - pointeur à pointeur
  • p - pointeur
  • data - la chose indiquée, dans les exemples x
  • gras - variable en lecture seule

Aiguille

  • p data - int *p;
  • p data - int const *p;
  • p data - int * const p;
  • p data - int const * const p;

Pointeur à pointeur

  1. pp p data - int **pp;
  2. pp p data - int ** const pp;
  3. pp p data - int * const *pp;
  4. pp p data - int const **pp;
  5. pp p data - int * const * const pp;
  6. pp p data - int const ** const pp;
  7. pp p data - int const * const *pp;
  8. pp p données - 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);

N-niveaux de déréférencement

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);
0
Undefined Behavior