c++ clockwise Quelle est la différence entre const int*, const int*const et int const*?




int* c++ (11)

Je gâche toujours comment utiliser const int* , const int * const et int const * correctement. Existe-t-il un ensemble de règles définissant ce que vous pouvez et ne pouvez pas faire?

Je veux connaître toutes les choses à faire et à ne pas faire en termes d'affectations, de passage aux fonctions, etc.


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

  • int* - pointeur vers int
  • int const * - pointeur vers const int
  • int * const - const pointeur vers int
  • int const * const - const pointeur vers const int

Maintenant, le premier const peut être de chaque côté du type:

  • const int * == int const *
  • const int * const == int const * const

Si vous voulez vraiment devenir fou, vous pouvez faire des choses comme ça:

  • int ** - pointeur vers le pointeur vers int
  • int ** const - un pointeur const vers un pointeur vers un int
  • int * const * - un pointeur vers un pointeur const vers un int
  • int const ** - un pointeur vers un pointeur vers un int int
  • int * const * const - un pointeur const vers un pointeur const vers un int
  • ...

Et pour s'assurer que nous sommes clair sur la signification 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 variable vers un entier constant. Cela vous permet de changer ce que vous pointez mais pas la valeur que vous pointez. Le plus souvent, cela est vu avec des chaînes de style C où vous avez un pointeur vers un const char . Vous pouvez changer la chaîne que vous pointez mais vous ne pouvez pas changer le contenu de ces chaînes. Ceci est important lorsque la chaîne se trouve dans le segment de données d'un programme et ne doit pas être modifiée.

bar est un pointeur fixe ou fixe vers une valeur qui peut être modifiée. C'est comme une référence sans le sucre syntaxique supplémentaire. De ce fait, vous utiliseriez généralement une référence dans laquelle vous utiliseriez un pointeur T* const moins que vous n'ayez besoin de pointeurs NULL .


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 si c'était une variable mais on ajoute 'const' avant elle. On doit l'initialiser immédiatement dans le constructeur car, bien sûr, on ne peut pas définir la valeur plus tard car cela la modifierait. Par exemple,

const int Constant1=96; 

va créer une constante entière, dénuée d'imagination 'Constant1', avec la valeur 96.

Ces constantes sont utiles pour les paramètres qui sont utilisés dans le programme mais n'ont pas besoin d'être modifiés après la compilation du programme. Il a un avantage pour les programmeurs sur la commande '#define' du préprocesseur C en ce qu'il est compris et utilisé par le compilateur lui-même, non seulement substitué dans le programme par le préprocesseur avant d'atteindre le compilateur principal, donc les messages d'erreur sont beaucoup plus utiles .

Il fonctionne également avec des 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 vers 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 pointeur constant vers un entier variable et

int const * const Constant4

déclare que Constant4 est un pointeur constant vers un entier constant. Fondamentalement, 'const' s'applique à tout ce qui est sur sa gauche immédiate (autre que s'il n'y a rien, auquel cas il s'applique à ce qui est son droit immédiat).

ref: http://duramecho.com/ComputerInformation/WhyHowCppConst.html


C'est simple mais difficile. Veuillez noter que nous pouvons échanger le qualificatif const avec n'importe quel type de données ( int , char , float , etc.).

Voyons voir les exemples ci-dessous.

const int *p ==> *p est en lecture seule [ p est un pointeur vers un entier constant]

int const *p ==> *p est en lecture seule [ p est un pointeur vers un entier constant]

int *p const ==> Déclaration incorrecte . Le compilateur génère une erreur de syntaxe.

int *const p ==> p est en lecture seule [ p est un pointeur constant vers un entier]. Comme le pointeur p est ici en lecture seule, la déclaration et la définition doivent être au même endroit.

const int *p const ==> Déclaration incorrecte . Le compilateur génère 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 vers un entier constant]. Comme le pointeur p est ici en lecture seule, la déclaration et la définition doivent être au même endroit.

int const *p const ==> Déclaration incorrecte . Le compilateur génère une erreur de syntaxe.

int const int *p ==> Déclaration incorrecte . Le compilateur génère 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 vers un entier constant]. Comme le pointeur p est ici en lecture seule, la déclaration et la définition doivent être au même endroit.


Je pense que tout est déjà répondu ici, mais je veux juste ajouter que vous devriez vous méfier des typedef s! Ils ne sont pas seulement des remplacements de texte.

Par exemple:

typedef char *ASTRING;
const ASTRING astring;

Le type d' astring est char * const , pas const char * . C'est une raison pour laquelle j'ai toujours tendance à mettre const à la droite du type, et jamais au début.


Il y a beaucoup d'autres points subtils entourant const correctness en C ++. Je suppose que la question ici a simplement été sur C, mais je vais donner quelques exemples connexes puisque le tag est C ++:

  • Vous passez souvent de gros arguments comme des chaînes comme TYPE const & qui empêche l'objet d'être modifié ou copié. 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 en tant que const , sinon vous ne pouvez pas appeler la méthode à partir d'une TYPE const & . Exemple :

    bool TYPE::operator==(const TYPE &rhs) const { ... }

  • Il existe des situations communes 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 les données de classe internes en tant que référence à non-const.

  • Par conséquent, il faut souvent 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; , alors 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) ); }

Afaik, il n'y a pas de fonctions const dans C, les fonctions non membres ne peuvent pas elles-mêmes être const dans C ++, les méthodes const peuvent avoir des effets secondaires, et le compilateur ne peut pas utiliser les fonctions const pour éviter les appels de fonction dupliqués. En fait, même un simple int const & référence pourrait être témoin de la valeur à laquelle il se réfère être changé ailleurs.


La règle générale est que le mot-clé const s'applique à ce qui le précède immédiatement. Exception, un const départ s'applique à ce qui suit.

  • const int* est identique à int const* et signifie "pointeur sur constant 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 ne suffit pas, pourriez-vous être plus précis sur ce que vous voulez?


Cette question montre précisément pourquoi j'aime faire les choses comme je l'ai mentionné dans ma question est const après type id acceptable?

En bref, je trouve que la façon la plus simple de se souvenir de la règle est que le "const" va après la chose à laquelle il s'applique. Donc dans votre question, "int const *" signifie que int est constant, alors que "int * const" signifierait que le pointeur est constant.

Si quelqu'un décide de le mettre à l'avant même (par exemple: "const int *"), comme une exception spéciale dans ce cas, il s'applique à la chose après.

Beaucoup de gens aiment utiliser cette exception spéciale parce qu'ils pensent que c'est plus joli. Je ne l'aime pas, parce que c'est une exception, et donc confond les choses.


Cela concerne principalement la deuxième ligne: les meilleures pratiques, les affectations, les paramètres de fonction, etc.

Pratique générale. Essayez de faire tout ce que vous pouvez const . Ou, pour le dire autrement, faites tout commencer par const , puis supprimez exactement l'ensemble minimum de const s nécessaires pour permettre au programme de fonctionner. Cela sera d'une grande aide pour atteindre la const-justesse, et aidera à s'assurer que les bogues subtils ne sont pas introduits lorsque les gens essaient d'assigner des choses qu'ils ne sont pas censés modifier.

Évitez const_cast <> comme la peste. Il y a un ou deux cas d'utilisation légitimes, mais ils sont très rares. Si vous essayez de changer un objet const , vous ferez mieux de trouver celui qui le déclarera const au premier pas et de discuter avec eux pour parvenir à un consensus sur ce qui devrait arriver.

Ce qui conduit très bien dans les affectations. Vous pouvez assigner en 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 - d'autres réponses ici ont admirablement couvert cette question, donc je n'entrerai pas dedans.

Paramètres de fonction:

Passez par la valeur: par exemple void func(int param) vous ne vous souciez pas d'un côté ou de l'autre sur le site appelant. On peut argumenter qu'il existe des cas d'utilisation pour déclarer la fonction void func(int const param) mais cela n'a aucun effet sur l'appelant, seulement sur la fonction elle-même, en ce que toute valeur passée ne peut pas être changée par la fonction pendant l'appel.

Passer par référence: par exemple void func(int &param) Maintenant, cela fait une différence. Comme vient d'être déclaré func est autorisé à changer param , et tout site appelant devrait être prêt à faire face aux conséquences. Changer la déclaration pour void func(int const &param) change le contrat, et garantit que func ne peut plus changer de param , ce qui signifie que ce qui est passé est ce qui va revenir. Comme d'autres l'ont noté, c'est très utile pour passer un objet volumineux que vous ne voulez pas changer. Passer une référence coûte beaucoup moins cher que de passer un objet volumineux en valeur.

Passer par pointeur: par exemple void func(int *param) et void func(int const *param) Ces deux sont à peu près synonymes de leurs homologues de référence, avec la mise en garde que la fonction appelée doit maintenant vérifier nullptr moins d'une autre garantie contractuelle assure à func qu'il ne recevra jamais de nullptr dans param .

Opinion sur ce sujet. Prouver l'exactitude dans un cas comme celui-ci est terriblement difficile, c'est juste trop facile de faire une erreur. Donc, ne prenez pas de risques, et vérifiez toujours les paramètres du pointeur pour nullptr . Vous allez vous épargner la douleur et la souffrance et difficile à trouver des bugs à long terme. Et en ce qui concerne le coût de la vérification, c'est peu cher, et dans les cas où l'analyse statique intégrée dans le compilateur peut le gérer, l'optimiseur l'élimine quand même. Activer la génération de code temporel de liaison pour MSVC, ou WOPR (je pense) pour GCC, et vous obtiendrez le programme à l'échelle, c'est-à-dire même dans les appels de fonction qui traversent une limite de module de code source.

À la fin de la journée tout ce qui précède fait un cas très solide pour toujours préférer les références aux pointeurs. Ils sont juste plus sûrs tout autour.


J'avais le même doute que toi jusqu'à ce que je tombe sur ce book du gourou C ++ Scott Meyers. Reportez-vous au troisième élément de ce livre où il parle en détail de l'utilisation de const .

Suivez simplement ce conseil

  1. Si le mot const apparaît à 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

Pour ceux qui ne connaissent pas Règle dans le sens des aiguilles d'une montre / spirale: Commencez par le nom de la variable, déplacez-vous dans le sens des aiguilles d'une montre (dans ce cas, reculez) jusqu'au pointeur ou au type suivant . Répétez jusqu'à ce que l'expression se termine.

voici une démo:


  1. Référence constante:

    Une référence à une variable (ici int), qui est constante. Nous passons la variable comme référence principalement, parce que les références sont de taille plus petite que la valeur réelle, mais il y a un effet secondaire et c'est parce que c'est comme un alias à la variable réelle. Nous pouvons accidentellement changer la variable principale grâce à notre accès complet à l'alias, donc nous le rendons constant pour éviter cet effet secondaire.

    int var0 = 0;
    const int &ptr1 = var0;
    ptr1 = 8; // Error
    var0 = 6; // OK
    
  2. Les pointeurs constants

    Une fois qu'un pointeur constant pointe vers une variable, il ne peut pointer vers aucune autre variable.

    int var1 = 1;
    int var2 = 0;
    
    int *const ptr2 = &var1;
    ptr2 = &var2; // Error
    
  3. Pointeur à constante

    Un pointeur à travers lequel on ne peut pas changer la valeur d'une variable pointée est connu sous le nom de pointeur vers la constante.

    int const * ptr3 = &var2;
    *ptr3 = 4; // Error
    
  4. Pointeur constant vers une constante

    Un pointeur constant vers une constante est un pointeur qui ne peut ni changer l'adresse vers laquelle elle pointe, ni changer la valeur conservée à cette adresse.

    int var3 = 0;
    int var4 = 0;
    const int * const ptr4 = &var3;
    *ptr4 = 1;     // Error
     ptr4 = &var4; // Error
    




const