const char * const versus const char *?

Eu estou correndo através de alguns programas de exemplo para me familiarizar com o C ++ e me deparo com a seguinte pergunta. Primeiro, aqui está o código de exemplo:

void print_string(const char * the_string) { cout << the_string << endl; } int main () { print_string("What's up?"); } 

No código acima, o parâmetro para print_string poderia ter sido const char * const the_string . Qual seria mais correto para isso?

Eu entendo que a diferença é que um é um ponteiro para um caractere constante, enquanto o outro é um ponteiro constante para um caractere constante. Mas por que ambos funcionam? Quando isso seria relevante?

Este último impede que você modifique the_string dentro de print_string . Seria realmente apropriado aqui, mas talvez a verbosidade deixasse o desenvolvedor.

char* the_string : Eu posso mudar o char para o qual the_string aponta, e eu posso modificar o char no qual ele aponta.

const char* the_string : Eu posso mudar o char para o qual the_string aponta, mas não posso modificar o char no qual ele aponta.

char* const the_string : Eu não posso mudar o char para o qual the_string aponta, mas eu posso modificar o char no qual ele aponta.

const char* const the_string : Não consigo alterar o char para o qual the_string aponta, nem posso modificar o char no qual ele aponta.

  1. Ponteiro mutável para um personagem mutável

     char *p; 
  2. Ponteiro mutável para um caractere constante

     const char *p; 
  3. Ponteiro constante para um personagem mutável

     char * const p; 
  4. Ponteiro constante para um caractere constante

     const char * const p; 

const char * const significa ponteiro, assim como os dados apontados pelo ponteiro, são ambos const!

const char * significa apenas os dados apontados pelo ponteiro, é const. ponteiro em si, porém, não é const.

Exemplo.

 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. 

(Eu sei que isso é velho, mas eu queria compartilhar de qualquer maneira.)

Só queria elaborar a resposta de Thomas Matthews. A regra esquerda-direita de declarações de tipo C praticamente diz: ao ler uma declaração de tipo C, inicie no identificador e vá para a direita quando puder e para a esquerda quando não puder.

Isto é melhor explicado com alguns exemplos:

Exemplo 1

  • Comece no identificador, não podemos ir para a direita, então vamos para a esquerda

     const char* const foo ^^^^^ 

    foo é uma constante

  • Continue à esquerda

     const char* const foo ^ 

    foo é um ponteiro constante para

  • Continue à esquerda

     const char* const foo ^^^^ 

    foo é um ponteiro constante para char

  • Continue à esquerda

     const char* const foo ^^^^^ 

    foo é um ponteiro constante para a constante char (Complete!)

Exemplo 2

  • Comece no identificador, não podemos ir para a direita, então vamos para a esquerda

     char* const foo ^^^^^ 

    foo é uma constante

  • Continue à esquerda

     char* const foo ^ 

    foo é um ponteiro constante para

  • Continue à esquerda

     char* const foo ^^^^ 

    foo é um ponteiro constante para char (Complete!)

Exemplo 1337

  • Comece pelo identificador, mas agora podemos dar certo!

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

    foo é uma matriz de 8

  • Pressione os parênteses para que não acerte mais, vá para a esquerda

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

    foo é uma matriz de 8 ponteiro para

  • Terminado entre parênteses, agora pode ir para a direita

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

    foo é uma matriz de 8 ponteiro para function que retorna

  • Nada mais para a direita, vá para a esquerda

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

    foo é uma matriz de 8 ponteiro para function que retorna um ponteiro para um

  • Continue à esquerda

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

    foo é uma matriz de 8 ponteiro para funções que retorna um ponteiro para uma constante

  • Continue à esquerda

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

    foo é uma matriz de 8 ponteiro para funções que retorna um ponteiro para um ponteiro constante para um

  • Continue à esquerda

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

    foo é uma matriz de 8 ponteiro para funções que retorna um ponteiro para um ponteiro constante para um char

  • Continue à esquerda

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

    foo é uma matriz de 8 ponteiro para funções que retorna um ponteiro para um ponteiro constante para uma constante char (Completo!)

Mais explicações: http://www.unixwiz.net/techtips/reading-cdecl.html

Muitas pessoas sugerem a leitura do especificador de tipo da direita para a esquerda.

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

Em ambos os formulários, o ponteiro está apontando para dados constantes ou somente leitura.

Na segunda forma, o ponteiro não pode ser alterado; o ponteiro sempre apontará para o mesmo lugar.

A diferença é que, sem a const extra, o programador pode mudar, dentro do método, para onde o ponteiro aponta; por exemplo:

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

Isso seria ilegal se a assinatura fosse void print_string(const char * const the_string)

Muitos programadores se sentem muito verbosos (na maioria dos cenários) a palavra-chave const adicional e a omite, mesmo que seja semanticamente correta.

Neste último você está garantindo não modificar o ponteiro e o caractere no primeiro, você apenas garante que o conteúdo não irá mudar, mas você pode mover o ponteiro ao redor

Não há razão para que um deles não funcione. Tudo print_string() faz é imprimir o valor. Não tenta modificá-lo.

É uma boa idéia fazer uma function que não modifique os argumentos de marca como const. A vantagem é que variables ​​que não podem mudar (ou você não quer mudar) podem ser passadas para essas funções sem erro.

Quanto à syntax exata, você deseja indicar quais tipos de argumentos são “seguros” para serem passados ​​para a function.

Eu acho que isso raramente é relevante, porque sua function não está sendo chamada com argumentos como & * the_string ou ** the_string. O próprio ponteiro é um argumento do tipo valor, portanto, mesmo se você modificá-lo, você não irá alterar a cópia que foi usada para chamar sua function. A versão que você está mostrando garante que a string não será alterada, e acho que isso é suficiente nesse caso.

const char * significa que você não pode usar o ponteiro para alterar o que é apontado. Você pode mudar o ponteiro para apontar para outra coisa, no entanto.

Considerar:

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

O parâmetro é um ponteiro não const para const char, então pode ser alterado para outro valor const char * (como uma string constante). Se, no entanto, escrevemos erroneamente *text = '\0' então teríamos um erro de compilation.

Indiscutivelmente, se você não pretende mudar para o que o parâmetro está apontando, você poderia fazer o parâmetro const char * const text , mas não é comum fazê-lo. Geralmente permitimos que as funções alterem os valores passados ​​para os parâmetros (porque passamos parâmetros por valor, qualquer alteração não afeta o chamador).

BTW: é uma boa prática evitar o char const * porque é muitas vezes mal interpretado – significa o mesmo que const char * , mas muitas pessoas o interpretam como char * const .

Quase todas as outras respostas estão corretas, mas elas perdem um aspecto disso: quando você usa a const extra em um parâmetro em uma declaração de function, o compilador essencialmente a ignora. Por um momento, vamos ignorar a complexidade de seu exemplo ser um ponteiro e apenas usar um int .

 void foo(const int x); 

declara a mesma function que

 void foo(int x); 

Somente na definição da function é o extra const significativo:

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

Esta definição é compatível com qualquer uma das declarações acima. O chamador não se importa que x seja um detalhe de implementação que não seja relevante no site da chamada.

Se você tiver um ponteiro const para dados const , as mesmas regras se aplicam:

 // 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 } 

Poucos programadores de C ++ se preocupam em fazer parâmetros const , mesmo quando poderiam ser, independentemente de esses parâmetros serem pointers.

A diferença entre os dois é que char * pode apontar para qualquer ponteiro arbitrário. Const char *, por outro lado, aponta para constantes definidas na seção DATA do executável. E, como tal, você não pode modificar os valores de caracteres de uma string const char *.