const char * const contre const char *?

110

Je suis en train de parcourir quelques exemples de programmes pour me familiariser avec C ++ et j'ai rencontré la question suivante. Tout d'abord, voici l'exemple de code:

void print_string(const char * the_string)
{
    cout << the_string << endl;
}

int main () {
    print_string("What's up?");
}

Dans le code ci-dessus, le paramètre de print_string aurait pu être à la place const char * const the_string. Qu'est-ce qui serait plus correct pour cela?

Je comprends que la différence est que l'un est un pointeur vers un caractère constant, tandis que l'autre est un pointeur constant vers un caractère constant. Mais pourquoi ces deux méthodes fonctionnent-elles? Quand cela serait-il pertinent?

pict
la source

Réponses:

244

Ce dernier vous empêche de modifier l' the_stringintérieur print_string. Ce serait en fait approprié ici, mais peut-être que la verbosité a découragé le développeur.

char* the_string: Je peux changer les points charvers lesquels the_string, et je peux modifier les charpoints auxquels il pointe.

const char* the_string: Je peux changer les points charvers lesquels the_string, mais je ne peux pas modifier les charpoints auxquels il pointe.

char* const the_string: Je ne peux pas changer les points charvers lesquels the_string, mais je peux modifier les charpoints auxquels il pointe.

const char* const the_string: Je ne peux pas changer les points charvers lesquels the_string, ni modifier les charpoints auxquels il pointe.

Kent Boogaart
la source
11
+1 pour la dernière phrase. La correction des constantes est verbeuse, mais ça vaut vraiment le coup.
mskfisher
6
@Xeo: votre formulaire est encore plus déroutant car c'est à une transposition de changer complètement de sens. const char *est beaucoup mieux parce que constc'est du côté complètement opposé.
R .. GitHub STOP HELPING ICE
7
@R ..: Eh bien, au moins pour moi ce n'est pas le cas. En lisant de droite à gauche, j'obtiens "pointer to const char". Pour moi, ça va mieux comme ça.
Xeo
6
Eh bien, vous vous trompez parce que les types C sont lus de l'intérieur, pas de la gauche vers la droite. :-)
R .. GitHub STOP AIDER ICE
11
Je suis un peu gêné, je suis apparemment le seul à ne pas comprendre cela ... mais quelle est la différence entre "le caractère vers lequel il pointe" et "le caractère vers lequel il pointe"?
Manque le
138
  1. Pointeur mutable vers un caractère mutable

    char *p;
  2. Pointeur mutable vers un caractère constant

    const char *p;
  3. Pointeur constant vers un caractère mutable

    char * const p; 
  4. Pointeur constant vers un caractère constant

    const char * const p;
James Michael Hare
la source
Cela ne devrait-il pas être: const char* p; --> constant pointer to mutable characteret char *const p; --> mutable pointer to constant character
PnotNP
2
@NulledPointer Non. Les déclarations C ++ sont formées de droite à gauche. Vous pouvez donc lire const char * pcomme: "p est un pointeur vers une constante de caractère" ou un pointeur mutable vers un caractère constant comme James l'affirme correctement. même chose avec le second :).
Samidamaru
28

const char * constsignifie que le pointeur ainsi que les données sur lesquelles le pointeur a pointé sont tous les deux const!

const char *signifie que seules les données sur lesquelles le pointeur a pointé sont const. Le pointeur lui-même n'est cependant pas const.

Exemple.

const char *p = "Nawaz";
p[2] = 'S'; //error, changing the const data!
p="Sarfaraz"; //okay, changing the non-const pointer. 

const char * const p = "Nawaz";
p[2] = 'S'; //error, changing the const data!
p="Sarfaraz"; //error, changing the const pointer. 
Nawaz
la source
20

(Je sais que c'est vieux mais je voulais quand même partager.)

Je voulais juste développer la réponse de Thomas Matthews. La règle droite-gauche des déclarations de type C dit à peu près: lors de la lecture d'une déclaration de type C, commencez par l'identifiant et allez à droite quand vous le pouvez et à gauche quand vous ne pouvez pas.

Ceci est mieux expliqué avec quelques exemples:

Exemple 1

  • Commencez par l'identifiant, on ne peut pas aller à droite donc on va à gauche

    const char* const foo
                ^^^^^

    foo est une constante ...

  • Continuer à gauche

    const char* const foo
              ^

    foo est un pointeur constant vers ...

  • Continuer à gauche

    const char* const foo
          ^^^^

    foo est un pointeur constant vers char ...

  • Continuer à gauche

    const char* const foo
    ^^^^^

    foo est un pointeur constant vers la constante char (Complete!)

Exemple 2

  • Commencez par l'identifiant, on ne peut pas aller à droite donc on va à gauche

    char* const foo
          ^^^^^

    foo est une constante ...

  • Continuer à gauche

    char* const foo
        ^

    foo est un pointeur constant vers ...

  • Continuer à gauche

    char* const foo
    ^^^^

    foo est un pointeur constant vers char (Complete!)

Exemple 1337

  • Commencez par l'identifiant, mais maintenant nous pouvons aller bien!

    const char* const* (*foo[8])()
                            ^^^

    foo est un tableau de 8 ...

  • Appuyez sur les parenthèses pour ne plus pouvoir aller à droite, allez à gauche

    const char* const* (*foo[8])()
                        ^

    foo est un tableau de 8 pointeurs vers ...

  • Terminé entre parenthèses, peut maintenant aller à droite

    const char* const* (*foo[8])()
                                ^^

    foo est un tableau de 8 pointeurs vers la fonction qui renvoie ...

  • Rien de plus à droite, va à gauche

    const char* const* (*foo[8])()
                     ^

    foo est un tableau de 8 pointeurs vers une fonction qui renvoie un pointeur vers un ...

  • Continuer à gauche

    const char* const* (*foo[8])()
                ^^^^^

    foo est un tableau de 8 pointeurs vers des fonctions qui renvoie un pointeur vers une constante ...

  • Continuer à gauche

    const char* const* (*foo[8])()
              ^

    foo est un tableau de 8 pointeurs vers des fonctions qui renvoie un pointeur vers un pointeur constant vers un ...

  • Continuer à gauche

    const char* const* (*foo[8])()
          ^^^^

    foo est un tableau de 8 pointeurs vers des fonctions qui renvoie un pointeur vers un pointeur constant vers un char ...

  • Continuer à gauche

    const char* const* (*foo[8])()
    ^^^^^

    foo est un tableau de 8 pointeurs vers des fonctions qui renvoie un pointeur vers un pointeur constant vers une constante char (Complete!)

Explication supplémentaire: http://www.unixwiz.net/techtips/reading-cdecl.html

Garrett
la source
CMIIW, const char * const foo doit être équivalent à char const * const foo?
luochenhuan
@luochenhuan Oui, c'est vrai.
Garrett
12

De nombreuses personnes suggèrent de lire le spécificateur de type de droite à gauche.

const char * // Pointer to a `char` that is constant, it can't be changed.
const char * const // A const pointer to const data.

Dans les deux formes, le pointeur pointe vers des données constantes ou en lecture seule.

Dans la seconde forme, le pointeur ne peut pas être modifié; le pointeur pointera toujours vers le même endroit.

Thomas Matthews
la source
3

La différence est que sans le supplément, constle programmeur pourrait changer, à l'intérieur de la méthode, l'endroit où le pointeur pointe; par exemple:

 void print_string(const char * the_string)
 {
    cout << the_string << endl;
    //....
    the_string = another_string();
    //....

 }

Ce serait plutôt illégal si la signature était void print_string(const char * const the_string)

De nombreux programmeurs se sentent trop verbeux (dans la plupart des scénarios) le constmot-clé supplémentaire et l'omettent, même s'il serait sémantiquement correct.

Leonbloy
la source
2

Dans ce dernier, vous garantissez de ne pas modifier à la fois le pointeur et le caractère dans le premier, vous garantissez seulement que le contenu ne changera pas mais vous pouvez déplacer le pointeur

Jésus Ramos
la source
Ahh donc sans le const final, je pourrais en fait définir le pointeur pour qu'il pointe vers une chaîne entièrement différente?
pict
Oui, sans ce const final, vous pouvez utiliser le pointeur de paramètre pour faire une itération par arithmétique de pointeur, où s'il y avait ce const, vous deviez créer votre propre pointeur qui est une copie de ce paramètre.
Jesus Ramos
2

Il n'y a aucune raison pour que l'un ou l'autre ne fonctionne pas. Il print_string()suffit d'imprimer la valeur. Il n'essaye pas de le modifier.

C'est une bonne idée de créer une fonction qui ne modifie pas les arguments de marque comme const. L'avantage est que les variables qui ne peuvent pas changer (ou que vous ne voulez pas changer) peuvent être passées à ces fonctions sans erreur.

En ce qui concerne la syntaxe exacte, vous souhaitez indiquer le type d'arguments "sûrs" à transmettre à la fonction.

Jonathan Wood
la source
2

Je pense que cela varie rarement, car votre fonction n'est pas appelée avec des arguments comme & * the_string ou ** the_string. Le pointeur lui-même est un argument de type valeur, donc même si vous le modifiez, vous n'allez pas changer la copie qui a été utilisée pour appeler votre fonction. La version que vous montrez garantit que la chaîne ne changera pas, et je pense que c'est suffisant dans ce cas.

Eglin
la source
2

const char *signifie que vous ne pouvez pas utiliser le pointeur pour modifier ce qui est pointé. Vous pouvez cependant changer le pointeur pour qu'il pointe vers autre chose.

Considérer:

const char * promptTextWithDefault(const char * text)
{
    if ((text == NULL) || (*text == '\0'))
        text = "C>";
    return text;
}

Le paramètre est un pointeur non const vers const char, il peut donc être remplacé par une autre const char *valeur (comme une chaîne constante). Si, cependant, nous avons écrit par erreur*text = '\0' nous obtiendrions une erreur de compilation.

On peut soutenir que si vous n'avez pas l'intention de modifier ce vers quoi le paramètre pointe, vous pouvez créer le paramètre const char * const text, mais ce n'est pas courant de le faire. Nous autorisons généralement les fonctions à modifier les valeurs passées aux paramètres (car nous transmettons les paramètres par valeur, tout changement n'affecte pas l'appelant).

BTW: c'est une bonne pratique à éviter char const *car il est souvent mal lu - cela signifie la même chose que const char *, mais trop de gens le lisent comme un sens char * const.

TonyR
la source
Hou la la! J'essayais de comprendre la différence entre ma const char *et la signature char const *- la façon dont vous avez formulé votre BTW a vraiment aidé!
sage
1

Presque toutes les autres réponses sont correctes, mais elles manquent un aspect de ceci: lorsque vous utilisez le supplément constsur un paramètre dans une déclaration de fonction, le compilateur l'ignorera essentiellement. Pendant un moment, ignorons la complexité de votre exemple étant un pointeur et utilisons simplement un int.

void foo(const int x);

déclare la même fonction que

void foo(int x);

Ce n'est que dans la définition de la fonction que le supplément est constsignificatif:

void foo(const int x) {
    // do something with x here, but you cannot change it
}

Cette définition est compatible avec l'une ou l'autre des déclarations ci-dessus. L'appelant ne se soucie pas que ce xsoitconst --Que est un détail de mise en œuvre qui ne sont pas pertinentes sur le site d'appel.

Si vous avez un constpointeur vers des constdonnées, les mêmes règles s'appliquent:

// these declarations are equivalent
void print_string(const char * const the_string);
void print_string(const char * the_string);

// In this definition, you cannot change the value of the pointer within the
// body of the function.  It's essentially a const local variable.
void print_string(const char * const the_string) {
    cout << the_string << endl;
    the_string = nullptr;  // COMPILER ERROR HERE
}

// In this definition, you can change the value of the pointer (but you 
// still can't change the data it's pointed to).  And even if you change
// the_string, that has no effect outside this function.
void print_string(const char * the_string) {
    cout << the_string << endl;
    the_string = nullptr;  // OK, but not observable outside this func
}

Peu de programmeurs C ++ se donnent la peine de créer des paramètres const, même lorsqu'ils pourraient l'être, que ces paramètres soient ou non des pointeurs.

Adrian McCarthy
la source
"le compilateur l'ignorera essentiellement" pas toujours vrai, Visual C ++ 2015 produirait un avertissement si vous ajoutez le supplément constà votre paramètre de fonction dans la définition mais pas dans la déclaration.
raymai97
@ raymai97: Je pense que c'est un bogue dans le compilateur 2015, mais je n'ai pas 2015 à portée de main à tester. Je travaille comme je l'ai décrit en 2017, ce qui, d'après mes conversations avec certains experts en normes, est le comportement attendu.
Adrian McCarthy
-1

La différence entre les deux est que char * peut pointer vers n'importe quel pointeur arbitraire. Const char * en revanche, pointe vers les constantes définies dans la section DATA de l'exécutable. Et, en tant que tel, vous ne pouvez pas modifier les valeurs de caractères d'une chaîne const char *.

Maz
la source
Je ne demande pas la différence entre char * et const char *. Je demande entre const char * et const char * const
pict
Cette réponse est incorrecte. Un const char*peut pointer partout où il le souhaite.
Cubic