Aquí hay una explicación detallada que espero sea útil. Comencemos con su programa, ya que es el más simple de explicar.
int main()
{
const char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
La primera declaración:
const char* p = "Hello";
declara p
como puntero a char
. Cuando decimos "puntero a un char
", ¿qué significa eso? Significa que el valor de p
es la dirección de a char
; p
nos dice dónde en la memoria hay un espacio reservado para guardar a char
.
La instrucción también se inicializa p
para señalar el primer carácter en el literal de cadena "Hello"
. Por el bien de este ejercicio, es importante entender p
que no apunta a toda la cadena, sino solo al primer carácter 'H'
,. Después de todo, p
es un puntero a uno char
, no a toda la cadena. El valor de p
es la dirección de la 'H'
en "Hello"
.
Luego configuras un bucle:
while (*p++)
¿Qué significa la condición de bucle *p++
? Aquí hay tres cosas que hacen que esto sea desconcertante (al menos hasta que se establezca la familiaridad):
- La precedencia de los dos operadores, postfix
++
e indirection*
- El valor de una expresión de incremento de postfix
- El efecto secundario de una expresión de incremento de postfix
1. Precedencia . Un rápido vistazo a la tabla de precedencia para operadores le dirá que el incremento de postfix tiene una precedencia más alta (16) que la desreferencia / indirección (15). Esto significa que el complejo de expresión *p++
va a ser agrupados como: *(p++)
. Es decir, la *
parte se aplicará al valor de la p++
parte. Así que tomemos la p++
parte primero.
2. Valor de expresión de postfix . El valor de p++
es el valor de p
antes del incremento . Si usted tiene:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
la salida será:
7
8
porque i++
evalúa i
antes del incremento. Del mismo modo p++
se va a evaluar el valor actual de p
. Como sabemos, el valor actual de p
es la dirección de 'H'
.
Entonces ahora la p++
parte de *p++
ha sido evaluada; que es el valor actual de p
. Entonces *
sucede la parte. *(current value of p)
significa: acceder al valor en la dirección que posee p
. Sabemos que el valor en esa dirección es 'H'
. Entonces la expresión se *p++
evalúa como 'H'
.
Ahora espera un minuto, estás diciendo. Si se *p++
evalúa como 'H'
, ¿por qué eso no se 'H'
imprime en el código anterior? Ahí es donde entran los efectos secundarios .
3. Postfix expresión efectos secundarios . El postfix ++
tiene el valor del operando actual, pero tiene el efecto secundario de incrementar ese operando. ¿Eh? Eche un vistazo a ese int
código nuevamente:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
Como se señaló anteriormente, el resultado será:
7
8
Cuando i++
se evalúa en el primero printf()
, se evalúa a 7. Pero el estándar C garantiza que en algún momento antes de que el segundo printf()
comience a ejecutarse, el efecto secundario del ++
operador habrá tenido lugar. Es decir, antes de que printf()
ocurra lo segundo , i
se habrá incrementado como resultado del ++
operador en lo primero printf()
. Esto, por cierto, es una de las pocas garantías que ofrece el estándar sobre el momento de los efectos secundarios.
En su código, entonces, cuando *p++
se evalúa la expresión , se evalúa como 'H'
. Pero para cuando llegues a esto:
printf ("%c", *p)
ese molesto efecto secundario ha ocurrido. p
ha sido incrementado Whoa! Ya no apunta a 'H'
, sino a un pasado del personaje 'H'
: al 'e'
, en otras palabras. Eso explica su salida cockneyfied:
ello
De ahí el coro de sugerencias útiles (y precisas) en las otras respuestas: para imprimir la pronunciación recibida "Hello"
y no su contraparte cockney, necesita algo como
while (*p)
printf ("%c", *p++);
Mucho para eso. ¿Qué pasa con el resto? Usted pregunta sobre el significado de estos:
*ptr++
*++ptr
++*ptr
Acabamos de hablar sobre la primera, por lo que vamos a ver el segundo: *++ptr
.
Vimos en nuestra explicación anterior que el incremento de postfix p++
tiene cierta precedencia , un valor y un efecto secundario . El incremento de prefijo ++p
tiene el mismo efecto secundario que su contraparte postfix: incrementa su operando en 1. Sin embargo, tiene una precedencia diferente y un valor diferente .
El incremento de prefijo tiene una precedencia menor que el postfix; tiene prioridad 15. En otras palabras, tiene la misma prioridad que el operador de desreferencia / indirección *
. En una expresión como
*++ptr
lo importante no es la precedencia: los dos operadores son idénticos en precedencia. Entonces la asociatividad entra en acción. El incremento de prefijo y el operador de indirección tienen asociatividad derecha-izquierda. Debido a esa asociatividad, el operando ptr
se agrupará con el operador más a la derecha ++
antes que el operador más a la izquierda *
,. En otras palabras, la expresión se va a agrupar *(++ptr)
. Entonces, como con *ptr++
pero por una razón diferente, aquí también la *
parte se aplicará al valor de la ++ptr
parte.
Entonces, ¿cuál es ese valor? El valor de la expresión de incremento de prefijo es el valor del operando después del incremento . Esto lo convierte en una bestia muy diferente del operador de incremento de postfix. Digamos que tienes:
int i = 7;
printf ("%d\n", ++i);
printf ("%d\n", i);
El resultado será:
8
8
... diferente de lo que vimos con el operador postfix. Del mismo modo, si tiene:
const char* p = "Hello";
printf ("%c ", *p); // note space in format string
printf ("%c ", *++p); // value of ++p is p after the increment
printf ("%c ", *p++); // value of p++ is p before the increment
printf ("%c ", *p); // value of p has been incremented as a side effect of p++
la salida será:
H e e l // good dog
¿Ves por qué?
Ahora llegamos a la tercera expresión que preguntaste, ++*ptr
. Ese es el más complicado de todos, en realidad. Ambos operadores tienen la misma precedencia y asociatividad derecha-izquierda. Esto significa que la expresión se agrupará ++(*ptr)
. La ++
parte se aplicará al valor de la *ptr
parte.
Entonces si tenemos:
char q[] = "Hello";
char* p = q;
printf ("%c", ++*p);
El resultado sorprendentemente egoísta será:
I
¡¿Qué?! Bien, entonces la *p
parte se va a evaluar 'H'
. Luego ++
entra en juego, en ese momento, se aplicará al 'H'
puntero, ¡no al puntero en absoluto! ¿Qué sucede cuando agregas 1 a 'H'
? Obtiene 1 más el valor ASCII de 'H'
72; se obtiene 73. Representar que como char
, y se obtiene el char
con el valor ASCII de 73: 'I'
.
Eso se encarga de las tres expresiones que preguntaste en tu pregunta. Aquí hay otro, mencionado en el primer comentario a su pregunta:
(*ptr)++
Ese también es interesante. Si usted tiene:
char q[] = "Hello";
char* p = q;
printf ("%c", (*p)++);
printf ("%c\n", *p);
te dará esta salida entusiasta:
HI
¿Que esta pasando? Nuevamente, es una cuestión de precedencia , valor de expresión y efectos secundarios . Debido a los paréntesis, la *p
parte se trata como una expresión primaria. Las expresiones primarias triunfan sobre todo lo demás; son evaluados primero. Y *p
, como sabes, evalúa a 'H'
. El resto de la expresión, la ++
parte, se aplica a ese valor. Entonces, en este caso, se (*p)++
convierte 'H'++
.
¿Cuál es el valor de 'H'++
? Si dijiste 'I'
, has olvidado (¡ya!) Nuestra discusión sobre el valor frente a los efectos secundarios con el incremento de postfix. Recuerde, 'H'++
evalúa el valor actual de 'H'
. Entonces eso primero printf()
se va a imprimir 'H'
. Luego, como efecto secundario , 'H'
se incrementará a 'I'
. El segundo printf()
imprime eso 'I'
. Y tienes tu alegre saludo.
De acuerdo, pero en esos dos últimos casos, ¿por qué necesito
char q[] = "Hello";
char* p = q;
¿Por qué no puedo tener algo como
/*const*/ char* p = "Hello";
printf ("%c", ++*p); // attempting to change string literal!
Porque "Hello"
es una cadena literal. Si lo intentas ++*p
, estás tratando de cambiar 'H'
la cadena a 'I'
, haciendo que toda la cadena "Iello"
. En C, los literales de cadena son de solo lectura; intentar modificarlos invoca un comportamiento indefinido. "Iello"
no está definido en inglés también, pero eso es solo una coincidencia.
Por el contrario, no puedes tener
char p[] = "Hello";
printf ("%c", *++p); // attempting to modify value of array identifier!
Por qué no? Porque en este caso, p
es una matriz. Una matriz no es un valor l modificable; no puede cambiar los p
puntos por incremento o decremento previo o posterior, porque el nombre de la matriz funciona como si fuera un puntero constante. (Eso no es lo que realmente es; esa es solo una forma conveniente de verlo).
En resumen, estas son las tres cosas que preguntaste:
*ptr++ // effectively dereferences the pointer, then increments the pointer
*++ptr // effectively increments the pointer, then dereferences the pointer
++*ptr // effectively dereferences the pointer, then increments dereferenced value
Y aquí hay un cuarto, tan divertido como los otros tres:
(*ptr)++ // effectively forces a dereference, then increments dereferenced value
El primero y el segundo se bloquearán si en ptr
realidad es un identificador de matriz. El tercero y el cuarto se bloquearán si ptr
apunta a una cadena literal.
Ahí tienes. Espero que todo sea cristal ahora. Has sido una gran audiencia, y estaré aquí toda la semana.
(*ptr)++
(paréntesis necesarios para desambiguar*ptr++
)