const char * const versus const char *?

110

Estoy ejecutando algunos programas de ejemplo para volver a familiarizarme con C ++ y me he encontrado con la siguiente pregunta. Primero, aquí está el código de ejemplo:

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

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

En el código anterior, el parámetro de print_string podría haber sido const char * const the_string. ¿Cuál sería más correcto para esto?

Entiendo que la diferencia es que uno es un puntero a un carácter constante, mientras que el otro es un puntero constante a un carácter constante. Pero, ¿por qué funcionan ambos? ¿Cuándo sería relevante?

picto
fuente

Respuestas:

244

Este último le impide modificar el the_stringinterior print_string. En realidad, sería apropiado aquí, pero quizás la verbosidad desanime al desarrollador.

char* the_string: Puedo cambiar chara qué the_stringpuntos y puedo modificar chara qué apunta.

const char* the_string: Puedo cambiar chara qué the_stringpuntos, pero no puedo modificar chara qué apunta.

char* const the_string: No puedo cambiar chara qué the_stringpuntos, pero puedo modificar chara qué apunta.

const char* const the_string: No puedo cambiar chara qué the_stringpuntos, ni puedo modificar chara qué apunta.

Kent Boogaart
fuente
11
+1 para la última oración. La corrección constante es detallada, pero vale la pena.
mskfisher
6
@Xeo: su forma es aún más confusa porque está a una transposición de cambiar su significado por completo. const char *es mucho mejor porque constestá en el lado completamente opuesto.
R .. GitHub DEJA DE AYUDAR A ICE
7
@R ..: Bueno, al menos para mí no lo es. Al leer de derecha a izquierda, obtengo "puntero a carácter constante". Para mí, se siente mejor así.
Xeo
6
Bueno, te estás engañando porque los tipos C se leen de adentro hacia afuera, no de izquierda a derecha. :-)
R .. GitHub DEJA DE AYUDAR A ICE
11
Estoy un poco avergonzado de ser aparentemente el único que no entiende esto ... pero ¿cuál es la diferencia entre "el carácter al que apunta" y "el carácter al que apunta"?
Falta el
138
  1. Puntero mutable a un personaje mutable

    char *p;
  2. Puntero mutable a un carácter constante

    const char *p;
  3. Puntero constante a un personaje mutable

    char * const p; 
  4. Puntero constante a un carácter constante

    const char * const p;
James Michael Hare
fuente
No debería ser esto: const char* p; --> constant pointer to mutable charactery char *const p; --> mutable pointer to constant character
PnotNP
2
@NulledPointer No. Las declaraciones de C ++ se forman de derecha a izquierda. Por lo tanto, puede leer const char * pcomo: "p es un puntero a una constante de carácter" o un puntero mutable a un carácter constante, como James dice correctamente. lo mismo con el segundo :).
Samidamaru
28

const char * constmedios puntero, así como los datos del puntero apuntada, son tanto const!

const char *significa que solo los datos a los que apunta el puntero son constantes. sin embargo, el puntero en sí no es constante.

Ejemplo.

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
fuente
20

(Sé que esto es antiguo, pero quería compartirlo de todos modos).

Solo quería desarrollar la respuesta de Thomas Matthews. La regla derecha-izquierda de las declaraciones de tipo C dice más o menos: cuando lea una declaración de tipo C, comience en el identificador y vaya a la derecha cuando pueda y a la izquierda cuando no pueda.

Esto se explica mejor con un par de ejemplos:

Ejemplo 1

  • Comience en el identificador, no podemos ir a la derecha, así que vamos a la izquierda

    const char* const foo
                ^^^^^

    foo es una constante ...

  • Continuar a la izquierda

    const char* const foo
              ^

    foo es un puntero constante a ...

  • Continuar a la izquierda

    const char* const foo
          ^^^^

    foo es un puntero constante a char ...

  • Continuar a la izquierda

    const char* const foo
    ^^^^^

    foo es un puntero constante a char constante (¡Completo!)

Ejemplo 2

  • Comience en el identificador, no podemos ir a la derecha, así que vamos a la izquierda

    char* const foo
          ^^^^^

    foo es una constante ...

  • Continuar a la izquierda

    char* const foo
        ^

    foo es un puntero constante a ...

  • Continuar a la izquierda

    char* const foo
    ^^^^

    foo es un puntero constante a char (¡Completo!)

Ejemplo 1337

  • Empiece por el identificador, ¡pero ahora podemos ir a la derecha!

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

    foo es una matriz de 8 ...

  • Presiona el paréntesis para que ya no puedas ir a la derecha, ve a la izquierda

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

    foo es una matriz de 8 punteros a ...

  • Terminado entre paréntesis, ahora puede ir a la derecha

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

    foo es una matriz de 8 punteros a la función que devuelve ...

  • Nada más a la derecha, ve a la izquierda

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

    foo es una matriz de 8 punteros a la función que devuelve un puntero a un ...

  • Continuar a la izquierda

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

    foo es una matriz de 8 punteros a funciones que devuelve un puntero a una constante ...

  • Continuar a la izquierda

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

    foo es una matriz de 8 punteros a funciones que devuelve un puntero a un puntero constante a un ...

  • Continuar a la izquierda

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

    foo es una matriz de 8 punteros a funciones que devuelve un puntero a un puntero constante a un carácter ...

  • Continuar a la izquierda

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

    foo es una matriz de 8 punteros a funciones que devuelve un puntero a una constante puntero a una constante char (¡Completo!)

Más explicación: http://www.unixwiz.net/techtips/reading-cdecl.html

Garrett
fuente
CMIIW, const char * const foo debería ser equivalente a char const * const foo?
luochenhuan
@luochenhuan Sí, de hecho lo son.
Garrett
12

Mucha gente sugiere leer el especificador de tipo de derecha a izquierda.

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

En ambas formas, el puntero apunta a datos constantes o de solo lectura.

En la segunda forma, el puntero no se puede cambiar; el puntero siempre apuntará al mismo lugar.

Thomas Matthews
fuente
3

La diferencia es que sin el extra, constel programador podría cambiar, dentro del método, a donde apunta el puntero; por ejemplo:

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

 }

En cambio, eso sería ilegal si la firma fuera void print_string(const char * const the_string)

Muchos programadores sienten que la constpalabra clave adicional es demasiado detallada (en la mayoría de los escenarios) y la omiten, aunque sería semánticamente correcta.

Leonbloy
fuente
2

En este último te estás garantizando no modificar tanto el puntero como el carácter en el primero solo garantizas que el contenido no cambiará pero puedes mover el puntero

Jesús Ramos
fuente
Ahh, así que sin la constante final, ¿podría configurar el puntero para que apunte a una cadena completamente diferente?
pict
Sí, sin esa constante final, puede usar el puntero de parámetro para hacer una iteración mediante la aritmética de puntero, donde si existía esa constante, tenía que crear su propio puntero, que es una copia de ese parámetro.
Jesus Ramos
2

No hay ninguna razón por la que ninguno de los dos no funcione. Todo lo que print_string()hace es imprimir el valor. No intenta modificarlo.

Es una buena idea hacer funciones que no modifiquen los argumentos de marca como const. La ventaja es que las variables que no pueden cambiar (o que no desea cambiar) se pueden pasar a estas funciones sin errores.

En cuanto a la sintaxis exacta, desea indicar qué tipo de argumentos son "seguros" para pasar a la función.

Jonathan Wood
fuente
2

Creo que rara vez es relevante, porque su función no se llama con argumentos como & * the_string o ** the_string. El puntero en sí es un argumento de tipo valor, por lo que incluso si lo modifica, no cambiará la copia que se usó para llamar a su función. La versión que está mostrando asegura que la cadena no cambiará, y creo que eso es suficiente en este caso.

Eglin
fuente
2

const char *significa que no puede usar el puntero para cambiar lo que se apunta. Sin embargo, puede cambiar el puntero para que apunte a otra cosa.

Considerar:

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

El parámetro es un puntero no constante a const char, por lo que se puede cambiar a otro const char *valor (como una cadena constante). Sin embargo, si escribimos por error*text = '\0' , obtendríamos un error de compilación.

Podría decirse que si no tiene la intención de cambiar a qué apunta el parámetro, podría hacer que el parámetro const char * const text , pero no es común hacerlo. Por lo general, permitimos que las funciones cambien los valores pasados ​​a los parámetros (debido a que pasamos los parámetros por valor, cualquier cambio no afecta al llamador).

Por cierto: es una buena práctica evitarlo char const *porque a menudo se const char *lee mal; significa lo mismo que , pero demasiada gente lo lee como significado char * const.

TonyR
fuente
¡Guauu! Estaba tratando de averiguar la diferencia entre mi const char *firma y la mía char const *, ¡la forma en que redactó su BTW realmente ayudó!
sabio
1

Casi todas las otras respuestas son correctas, pero pierden un aspecto de esto: cuando usa el extra consten un parámetro en una declaración de función, el compilador esencialmente lo ignorará. Por un momento, ignoremos la complejidad de que su ejemplo sea un puntero y usemos un int.

void foo(const int x);

declara la misma función que

void foo(int x);

Solo en la definición de la función es más constsignificativo:

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

Esta definición es compatible con cualquiera de las declaraciones anteriores. A la persona que llama no le importa que xseaconst un detalle de implementación que no es relevante en el sitio de la llamada.

Si tiene un constpuntero a constdatos, se aplican las mismas reglas:

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

Pocos programadores de C ++ se molestan en crear parámetros const, incluso cuando podrían serlo, independientemente de si esos parámetros son punteros.

Adrian McCarthy
fuente
"el compilador esencialmente lo ignorará" no siempre es cierto, Visual C ++ 2015 produciría una advertencia si agrega el extra consta su parámetro de función en la definición pero no en la declaración.
raymai97
@ raymai97: Creo que es un error en el compilador de 2015, pero no tengo 2015 a mano para probar. Trabajo como describí en 2017, que, según mis conversaciones con algunos expertos en estándares, es el comportamiento esperado.
Adrian McCarthy
-1

La diferencia entre los dos es que char * puede apuntar a cualquier puntero arbitrario. Const char * por el contrario, apunta a las constantes definidas en la sección DATA del ejecutable. Y, como tal, no puede modificar los valores de caracteres de una cadena const char *.

Maz
fuente
No estoy preguntando la diferencia entre char * y const char *. Estoy pidiendo entre const char * const char * y const
PICT
Esta respuesta es incorrecta. A const char*puede señalar a cualquier lugar que le plazca.
Cubic