const char * const versus const char *?

110

Estou examinando alguns programas de exemplo para me familiarizar novamente com C ++ e me deparei 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?

pict
fonte

Respostas:

244

Este último impede que você modifique por the_stringdentro print_string. Na verdade, seria apropriado aqui, mas talvez a verbosidade tenha desanimado o desenvolvedor.

char* the_string: Posso alterar o charpara quais the_stringpontos e posso modificar os charpara os quais aponta.

const char* the_string: Posso alterar o charpara quais the_stringpontos, mas não posso modificar os charpara os quais ele aponta.

char* const the_string: Não posso alterar o charpara quais the_stringpontos, mas posso modificar os charpara os quais ele aponta.

const char* const the_string: Não posso alterar o charpara o qual the_stringaponta, nem posso modificar o charpara o qual ele aponta.

Kent Boogaart
fonte
11
1 para a última frase. A correção constante é prolixa, mas vale a pena.
mskfisher de
6
@Xeo: seu formulário é ainda mais confuso porque está a uma transposição de mudar totalmente seu significado. const char *é muito melhor porque constestá no lado oposto completo.
R .. GitHub PARAR DE AJUDAR O ICE
7
@R ..: Bem, pelo menos para mim não é. Lendo da direita para a esquerda, obtenho "ponteiro para const char". Para mim, é melhor assim.
Xeo
6
Bem, você está se enganando porque os tipos C são lidos de dentro para fora, não da esquerda para a direita. :-)
R .. GitHub PARE DE AJUDAR O ICE
11
Estou um pouco envergonhado, aparentemente, sou o único que não entende isso ... mas qual é a diferença entre "o char para o qual ele aponta" e "o char para o qual ele aponta"?
Falta
138
  1. Ponteiro mutável para um caractere 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;
James Michael Hare
fonte
Não deveria ser: const char* p; --> constant pointer to mutable charactere char *const p; --> mutable pointer to constant character
PnotNP
2
@NulledPointer Não. As declarações C ++ são formadas da direita para a esquerda. Você pode, portanto, ler const char * pcomo: "p é um ponteiro para uma constante de caractere" ou um ponteiro mutável para um caractere constante, como James afirma corretamente. mesmo com o segundo :).
Samidamaru
28

const char * constsignifica que o ponteiro, assim como os dados para os quais o ponteiro apontou, são ambos const!

const char *significa que apenas os dados para os quais o ponteiro apontou são const. o ponteiro em si, entretanto, 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. 
Nawaz
fonte
20

(Eu sei que isso é antigo, mas queria compartilhar assim mesmo.)

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

Isso é 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 para a esquerda

    const char* const foo
              ^

    foo é um ponteiro constante para ...

  • Continue para a esquerda

    const char* const foo
          ^^^^

    foo é um ponteiro constante para char ...

  • Continue para a esquerda

    const char* const foo
    ^^^^^

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

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 para a esquerda

    char* const foo
        ^

    foo é um ponteiro constante para ...

  • Continue para a esquerda

    char* const foo
    ^^^^

    foo é um ponteiro constante para char (Completo!)

Exemplo 1337

  • Comece pelo identificador, mas agora podemos dar certo!

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

    foo é um array de 8 ...

  • Aperte parênteses para não poder mais ir para a direita, vá para a esquerda

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

    foo é uma matriz de 8 ponteiros para ...

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

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

    foo é uma matriz de 8 ponteiros para função que retorna ...

  • Nada mais à direita, vá para a esquerda

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

    foo é uma matriz de 8 ponteiros para função que retorna um ponteiro para ...

  • Continue para a esquerda

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

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

  • Continue para a esquerda

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

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

  • Continue para a esquerda

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

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

  • Continue para a esquerda

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

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

Explicação adicional: http://www.unixwiz.net/techtips/reading-cdecl.html

Garrett
fonte
CMIIW, const char * const foo deve ser equivalente a char const * const foo?
luochenhuan
@luochenhuan Sim, são mesmo.
Garrett de
12

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.

Thomas Matthews
fonte
3

A diferença é que sem o extra consto programador poderia 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 consideram muito prolixo (na maioria dos cenários) a constpalavra-chave extra e a omitem, mesmo que seja semanticamente correta.

leonbloy
fonte
2

Neste último você está garantindo que não vai modificar o ponteiro e o caractere no primeiro você só garante que o conteúdo não irá mudar mas você pode mover o ponteiro

Jesus Ramos
fonte
Ahh, então sem a const final, eu poderia definir o ponteiro para apontar para uma string totalmente diferente?
pict
Sim, sem essa const final você pode usar o ponteiro do parâmetro para fazer alguma iteração pela aritmética do ponteiro, onde se houvesse aquele const você teria que criar seu próprio ponteiro que é uma cópia desse parâmetro.
Jesus Ramos
2

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

É uma boa ideia criar uma função que não modifique os argumentos da marca como const. A vantagem é que as variáveis ​​que não podem ser alteradas (ou que você não deseja) podem ser passadas para essas funções sem erros.

No que diz respeito à sintaxe exata, você deseja indicar quais tipos de argumentos são "seguros" para serem transmitidos à função.

Jonathan Wood
fonte
2

Acho que variam raramente são relevantes, porque sua função não está sendo chamada com argumentos como & * the_string ou ** the_string. O ponteiro em si é um argumento de tipo de valor, portanto, mesmo se você modificá-lo, não mudará a cópia que foi usada para chamar sua função. A versão que você está mostrando garante que a string não mudará, e acho que isso é suficiente neste caso.

Eglin
fonte
2

const char *significa que você não pode usar o ponteiro para alterar o que é apontado. Você pode alterar 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, portanto, pode ser alterado para outro const char *valor (como uma string constante). Se, no entanto, escrevermos por engano *text = '\0', obteremos um erro de compilação.

Indiscutivelmente, se você não pretende alterar o que o parâmetro está apontando, você poderia fazer o parâmetro const char * const text, mas não é comum fazer isso. Normalmente permitimos que as funções alterem os valores passados ​​para os parâmetros (como passamos parâmetros por valor, qualquer mudança não afeta o chamador).

BTW: é uma boa prática evitar char const *porque muitas vezes é mal interpretado - significa o mesmo que const char *, mas muitas pessoas lêem como significado char * const.

TonyR
fonte
Uau! Eu estava tentando descobrir a diferença entre a minha const char *e a assinatura char const *- a maneira como você redigiu seu BTW realmente ajudou!
sábio
1

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

void foo(const int x);

declara a mesma função que

void foo(int x);

Apenas na definição da função o extra é constsignificativo:

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é const--que é um detalhe de implementação não é relevante no site da chamada.

Se você tiver um constponteiro para constdados, 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 C ++ se preocupam em criar parâmetros const, mesmo quando poderiam ser, independentemente de esses parâmetros serem ponteiros.

Adrian McCarthy
fonte
"o compilador irá essencialmente ignorá-lo" nem sempre verdadeiro, o Visual C ++ 2015 produziria um aviso se você adicionar o extra constao parâmetro de função na definição, mas não na declaração.
raymai97 01 de
@ raymai97: Acho que é um bug no compilador de 2015, mas não tenho 2015 em mãos para testar. Trabalho como descrevi em 2017, o que, de acordo com minhas conversas com alguns especialistas em padrões, é o comportamento esperado.
Adrian McCarthy
-1

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

Maz
fonte
Não estou perguntando a diferença entre char * e const char *. Estou perguntando entre const char * e const char * const
pict
Esta resposta está incorreta. A const char*pode apontar para qualquer lugar que desejar.
Cubic de