Expresiones de puntero: * ptr ++, * ++ ptr y ++ * ptr

Recientemente me he encontrado con este problema que no puedo entender por mí mismo.

¿Qué significan REALMENTE estas tres Expresiones?

*ptr++ *++ptr ++*ptr 

Lo he intentado con Ritchie. Pero desafortunadamente no pudo seguir lo que dijo sobre estas 3 operaciones.

Sé que todos se realizan para incrementar el puntero / el valor apuntado. También puedo adivinar que puede haber muchas cosas sobre la precedencia y el orden de evaluación. Al igual que uno incrementa el puntero primero y luego obtiene el contenido de ese puntero, uno simplemente busca el contenido y luego incrementa el puntero, etc. Como puede ver, no entiendo claramente sus operaciones reales , lo que me gustaría despejar tan pronto como sea posible. Pero estoy realmente perdido cuando tengo la oportunidad de aplicarlos en los progtwigs. Por ejemplo:

 int main() { const char *p = "Hello"; while(*p++) printf("%c",*p); return 0; } 

me da esta salida:

 ello 

Pero mi expectativa era que imprimiera Hello . Una solicitud final: proporcione ejemplos de cómo funciona cada expresión en un fragmento de código dado. Como la mayoría de las veces, solo un simple párrafo de teoría se me pasa por la cabeza.

Aquí hay una explicación detallada que espero sea útil. Comencemos con su progtwig, ya que es el más simple de explicar.

 int main() { const char *p = "Hello"; while(*p++) printf("%c",*p); return 0; } 

La primera statement:

 const char* p = "Hello"; 

declara p como un puntero a char . Cuando decimos “puntero a un char “, ¿qué significa eso? Significa que el valor de p es la dirección de un char ; p nos dice que en la memoria hay espacio reservado para contener un char .

La instrucción también inicializa p para apuntar al primer carácter en el literal de cadena "Hello" . Por el bien de este ejercicio, es importante entender que p no apunta a toda la cuerda, sino solo al primer caracter, 'H' . Después de todo, p es un puntero a un char , no a toda la cadena. El valor de p es la dirección de 'H' en "Hello" .

Luego configura un bucle:

 while (*p++) 

¿Qué significa la condición de bucle *p++ ? Hay tres cosas en juego que hacen que esto desconcierte (al menos hasta que la familiaridad se establece):

  1. La precedencia de los dos operadores, postfix ++ e indirección *
  2. El valor de una expresión de incremento de postfijo
  3. El efecto secundario de una expresión de incremento de postfix

1. Precedencia . Un rápido vistazo a la tabla de precedencia para los operadores le dirá que el incremento de postfijo tiene una precedencia más alta (16) que la desreferencia / indirección (15). Esto significa que la expresión compleja *p++ se va a agrupar como: *(p++) . Es decir, la parte * se aplicará al valor de la parte p++ . Así que tomemos la parte p++ 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++ va a evaluar el valor actual de p . Como sabemos, el valor actual de p es la dirección de 'H' .

Entonces ahora se ha evaluado la parte p++ de *p++ ; es el valor actual de p . Entonces la * parte sucede. *(current value of p) significa: acceder al valor en la dirección mantenida por p . Sabemos que el valor en esa dirección es 'H' . Entonces, la expresión *p++ evalúa como 'H' .

Ahora espera un minuto, estás diciendo. Si *p++ evalúa a 'H' , ¿por qué esa 'H' imprime en el código anterior? Ahí es donde entran los efectos secundarios .

3. Efectos secundarios de la expresión de Postfix . El postfix ++ tiene el valor del operando actual, pero tiene el efecto secundario de incrementar ese operando. ¿Huh? Eche un vistazo a ese código int nuevamente:

 int i = 7; printf ("%d\n", i++); printf ("%d\n", i); 

Como se señaló anteriormente, el resultado será:

 7 8 

Cuando se evalúa i++ en el primer printf() , se evalúa como 7. Pero el estándar C garantiza que en algún momento antes de que el segundo printf() comience a ejecutarse, se producirá el efecto secundario del operador ++ . Es decir, antes de que ocurra el segundo printf() , i habrá incrementado como resultado del operador ++ en el primer printf() . Por cierto, esta es una de las pocas garantías que ofrece la norma sobre el momento de los efectos secundarios.

En su código, cuando se evalúa la expresión *p++ , se evalúa como 'H' . Pero cuando llegas a esto:

 printf ("%c", *p) 

ese molesto efecto secundario ha ocurrido. p se ha incrementado. Whoa! Ya no apunta a 'H' , sino a un caracter pasado 'H' : a la 'e' , en otras palabras. Eso explica tu 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++); 

Demasiado para eso. ¿Qué pasa con el rest? Usted pregunta acerca de los significados de estos:

 *ptr++ *++ptr ++*ptr 

Acabamos de hablar sobre el primero, así que veamos el segundo: *++ptr .

Vimos en nuestra explicación anterior que el incremento posfix p++ tiene cierta precedencia , un valor y un efecto secundario . El incremento de prefijo ++p tiene el mismo efecto secundario que su contraparte de postfijo: 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 sufijo; tiene prioridad 15. En otras palabras, tiene la misma precedencia que el operador de desreferencia / indirección * . En una expresión como

 *++ptr 

lo que importa no es la precedencia: los dos operadores son idénticos en precedencia. Así que la asociatividad entra en juego. El incremento de prefijo y el operador de indirección tienen asociatividad de derecha a izquierda. Debido a esa asociatividad, el operando ptr se va a 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 agrupará *(++ptr) . Entonces, como con *ptr++ pero por una razón diferente, aquí también la parte * se va a aplicar al valor de la parte ++ptr .

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 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 eel // good dog 

¿Ves por qué?

Ahora llegamos a la tercera expresión sobre la que preguntaste, ++*ptr . Ese es el más complicado del lote, en realidad. Ambos operadores tienen la misma precedencia y asociatividad de derecha a izquierda. Esto significa que la expresión se agrupará ++(*ptr) . La parte ++ se aplicará al valor de la parte *ptr .

Entonces si tenemos:

 char q[] = "Hello"; char* p = q; printf ("%c", ++*p); 

la producción sorprendentemente egoísta va a ser:

 I 

¡¿Qué?! De acuerdo, entonces la parte *p va a evaluar a 'H' . Entonces el ++ entra en juego, en ese punto, se aplicará a la 'H' , ¡no al puntero en absoluto! ¿Qué sucede cuando agrega 1 a 'H' ? Obtiene 1 más el valor ASCII de 'H' , 72; obtienes 73. Representa eso como un char , y obtienes el char con el valor ASCII de 73: 'I' .

Eso se ocupa 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á este resultado entusiasta:

 HI 

¿Que esta pasando? De nuevo, es una cuestión de precedencia , valor de expresión y efectos secundarios . Debido a los paréntesis, la parte *p se trata como una expresión primaria. Las expresiones primarias prevalecen sobre todo lo demás; ellos son evaluados primero. Y *p , como saben, evalúa a 'H' . El rest de la expresión, la parte ++ , se aplica a ese valor. Entonces, en este caso, (*p)++ convierte en 'H'++ .

¿Cuál es el valor de 'H'++ ? Si dijo 'I' , ya olvidó (¡ya!) Nuestra discusión sobre el valor frente al efecto secundario con el incremento de postfijo. Recuerde, 'H'++ evalúa el valor actual de 'H' . Entonces esa primera printf() va a imprimir 'H' . Luego, como efecto secundario , esa 'H' se incrementará a 'I' . El segundo printf() imprime ese 'I' . Y tienes tu alegre saludo.

Está bien, pero en esos dos últimos casos, ¿por qué necesito

 char q[] = "Hello"; char* p = q; 

¿Por qué no puedo tener algo así como

 /*const*/ char* p = "Hello"; printf ("%c", ++*p); // attempting to change string literal! 

Porque "Hello" es un literal de cadena. Si intenta ++*p , está tratando de cambiar la 'H' en la cadena a 'I' , haciendo que toda la cadena sea "Iello" . En C, los literales de cadena son de solo lectura; intentar modificarlos invoca un comportamiento indefinido. "Iello" está definido en inglés, 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 dónde 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, es solo una manera conveniente de verlo).

Para resumir, aquí están las tres cosas sobre las 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 bloquean si ptr es realmente un identificador de matriz. El tercero y cuarto se bloqueará si ptr apunta a un literal de cadena.

Ahí tienes. Espero que todo sea cristal ahora. Has sido una gran audiencia, y estaré aquí toda la semana.

Supongamos que ptr apunta al elemento i-ésimo de la matriz arr .

  1. *ptr++ evalúa arr[i] y establece ptr para apuntar al elemento (i + 1) -th de arr . Es equivalente a *(ptr++) .

  2. *++ptr establece ptr para apuntar al elemento (i + 1) -th de arr y evalúa arr[i+1] . Es equivalente a *(++ptr) .

  3. ++*ptr aumenta arr[i] en uno y evalúa su valor aumentado; el puntero ptr se ptr . Es equivalente a ++(*ptr) .

También hay uno más, pero necesitaría paréntesis para escribirlo:

  1. (*ptr)++ aumenta arr[i] en uno y evalúa su valor antes de boost; el puntero ptr nuevamente se deja intacto.

El rest lo puedes descubrir tú mismo; también fue respondida por @Jaguar.

*ptr++ : post increment a pointer ptr

*++ptr : Pre Increment a pointer ptr

++*ptr : preincrement the value at ptr location

Lea aquí sobre los operadores de incremento previo y post incremento


Esto dará hello como salida

 int main() { const char *p = "Hello"; while(*p) printf("%c",*p++);//Increment the pointer here return 0; } 

La condición en tu ciclo es mala:

 while(*p++) printf("%c",*p); 

Es lo mismo que

 while(*p) { p++; printf("%c",*p); } 

Y eso está mal, esto debería ser:

 while(*p) { printf("%c",*p); p++; } 

*ptr++ es lo mismo que *(ptr++) , que es:

 const char *ptr = "example"; char value; value = *ptr; ++ptr; printf("%c", value); // will print 'e' 

*++ptr es lo mismo que *(++ptr) , que es:

 const char *ptr = "example"; char value; ++ptr; value = *ptr; printf("%c", value); // will print 'x' 

++*ptr es lo mismo que ++(*ptr) , que es:

 const char *ptr = "example"; char value; value = *ptr; ++value; printf("%c", value); // will print 'f' ('e' + 1) 

Tiene razón sobre la precedencia, tenga en cuenta que * tiene precedencia sobre el incremento de prefijo, pero no sobre el incremento de postfijo. Así es como estas rupturas:

*ptr++ – yendo de izquierda a derecha, desreferenciando el puntero y luego incrementando el valor del puntero (no lo que apunta, debido a la precedencia de postfix sobre la desreferencia)

*++ptr – incrementa el puntero y luego lo desreferencia, esto es porque el prefijo y la desreferencia tienen la misma precedencia y por lo tanto se evalúan en orden de derecha a izquierda

++*ptr – similar a la anterior en términos de precedencia, yendo de nuevo de derecha a izquierda para desreferenciar el puntero y luego incrementar a lo que apunta el puntero. Tenga en cuenta que en su caso esto dará lugar a un comportamiento indefinido porque está tratando de modificar una variable de solo lectura ( char* p = "Hello"; ).

Voy a agregar mi opinión porque, aunque las otras respuestas son correctas, creo que se están perdiendo algo.

  v = *ptr++ 

medio

  temp = ptr; ptr = ptr + 1 v = *temp; 

Donde como

  *++ptr 

medio

  ptr = ptr + 1 v = *ptr 

Es importante entender que el incremento posterior (y posterior decremento) significa

  temp = ptr // Temp created here!!! ptr = ptr + 1 // or - 1 if decrement) v = *temp // Temp destroyed here!!! 

¿Por qué eso importa? Bueno en C eso no es tan importante. En C ++, aunque ptr podría ser un tipo complejo como un iterador. Por ejemplo

  for (std::set::iterator it = someSet.begin(); it != someSet.end(); it++) 

En este caso, it es un tipo complejo it++ puede tener efectos secundarios debido a la creación de la temp . Por supuesto, si tienes suerte, el comstackdor intentará descartar el código que no es necesario, pero si el constructor o el destructor del iterador hacen algo it++ mostrará los efectos cuando crea la temp .

El corto de lo que estoy tratando de decir es Escribe lo que quieres decir . Si quiere decir incrementar ptr , escriba ++ptr no ptr++ . Si te refieres a temp = ptr, ptr += 1, temp entonces escribe ptr++

postfix y el prefijo tiene mayor precedencia que la referencia

* ptr ++ aquí incrementa ptr y luego apunta al nuevo valor de ptr

* ++ ptr aquí Pre Incrementa el puño y luego apunta al nuevo valor de ptr

++ * ptr aquí primero obtén el valor de ptr apuntando e incrementando ese valor

 *ptr++ // 1 

Esto es lo mismo que:

  tmp = *ptr; ptr++; 

Entonces se recupera el valor del objeto apuntado por ptr , luego se incrementa ptr .

 *++ptr // 2 

Esto es lo mismo que:

  ++ptr; tmp = *ptr; 

De modo que el puntero ptr se incrementa, luego se lee el objeto apuntado por ptr .

 ++*ptr // 3 

Esto es lo mismo que:

  ++(*ptr); 

Entonces el objeto apuntado por ptr se incrementa; ptr sí mismo no se modifica.

Expresiones de puntero: * ptr ++, * ++ ptr y ++ * ptr:

Nota : los punteros deben inicializarse y deben tener una dirección válida. Debido a que en la RAM, aparte de nuestro progtwig (a.out), hay mucho más progtwig ejecutándose simultáneamente, es decir, si intenta acceder a alguna memoria que no estaba reservada para su sistema operativo, lo hará a través de la falla de segmentación.

Antes de explicar esto, consideremos un ejemplo simple.

 #include int main() { int num = 300; int *ptr;//uninitialized pointer.. must be initialized ptr = # printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr); *ptr = *ptr + 1;//*ptr means value/data on the address.. so here value gets incremented printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr); /** observe here that "num" got changed but manually we didn't change, it got modified by pointer **/ ptr = ptr + 1;//ptr means address.. so here address got incremented /** char pointer gets incremented by 1 bytes Integer pointer gets incremented by 4 bytes **/ printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr); } 

analizar el resultado del código anterior, espero que haya obtenido el resultado del código anterior. Una cosa es clara desde el código anterior es que el nombre del puntero ( ptr ) significa que estamos hablando de la dirección y * ptr significa que estamos hablando de valor / datos.

CASO 1 : * ptr ++, * ++ ptr, * (ptr ++) y * (++ ptr):

Las cuatro syntax mencionadas anteriormente son similares, en todas las address gets incremented pero la forma en que se incrementa la dirección es diferente.

Nota : para resolver cualquier expresión, averigüe cuántos operadores hay en expresión y luego descubra las prioridades del operador. Tengo varios operadores que tienen la misma prioridad y luego verifico el orden de evolución o asociatividad que puede ser de derecha (R) a izquierda (L) o de izquierda a derecha.

* ptr ++ : Aquí 2 operadores están a saber de-referencia (*) y ++ (incremento). Ambos tienen la misma prioridad y luego verifican la asociatividad, que es de R a L. Así que comienza a resolver de derecha a izquierda, sin importar qué operadores vengan primero.

* ptr ++ : primero ++ se produjo mientras se resolvía de R a L, por lo que la dirección se incrementa pero su incremento en el puesto.

* ++ ptr : Igual que el primero aquí también la dirección se incrementa pero es un incremento previo.

* (ptr ++) : Aquí hay 3 operadores, entre ellos la agrupación () que tiene la más alta prioridad, así que primero ptr ++ resuelto, es decir, la dirección se incrementa pero se publica.

* (++ ptr) : al igual que en el caso anterior, la dirección también se incrementa pero se incrementa de manera previa.

CASO 2 : ++ * ptr, ++ (* ptr), (* ptr) ++:

arriba mencionado, las 4 syntax son similares, en todos los valores / datos se incrementa, pero cómo cambia el valor es diferente.

++ * ptr : primero * llegó mientras resolvía de R a L, por lo que el valor se cambia pero es un incremento previo.

++ (* ptr) : al igual que en el caso anterior, el valor se modifica.

(* ptr) ++ : Aquí hay 3 operadores, entre ellos la agrupación () que tiene la prioridad más alta, Inside () * ptr está ahí, entonces primero * ptr se resuelve, es decir, el valor se incrementa pero se publica.

Nota : ++ * ptr y * ptr = * ptr + 1 son iguales, en ambos casos se cambia el valor. ++ * ptr: solo se usa 1 instrucción (INC), directamente el valor se cambia en una sola toma. * ptr = * ptr + 1: aquí el primer valor se incrementa (INC) y luego se asigna (MOV).

Para entender todo lo anterior, la syntax de incremento diferente en el puntero permite considerar un código simple:

 #include int main() { int num = 300; int *ptr; ptr = # printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr); *ptr++;//address changed(post increment), value remains un-changed // *++ptr;//address changed(post increment), value remains un-changed // *(ptr)++;//address changed(post increment), value remains un-changed // *(++ptr);//address changed(post increment), value remains un-changed // ++*ptr;//value changed(pre increment), address remains un-changed // (*ptr)++;//value changed(pre increment), address remains un-changed // ++(*ptr);//value changed(post increment), address remains un-changed printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr); } 

En el código anterior, intente comentar / des-comentar comentarios y analizar resultados.

Punteros como constante : no hay formas en que pueda hacer que los punteros sean constantes, pocos de los que menciono aquí.

1) const int * p OR int const * p : Aquí el value es constante , la dirección no es constante , es decir, ¿dónde p está apuntando? ¿Alguna dirección? En esa dirección, ¿cuál es el valor? Algunos valoran ¿no? Ese valor es constante, no puede modificar ese valor, pero ¿hacia dónde apunta el puntero? Algunas direcciones ¿verdad? Puede señalar a otra dirección también.

Para entender esto, consideremos el siguiente código:

 #include int main() { int num = 300; const int *ptr;//constant value, address is modifible ptr = # printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr); *ptr++;// // *++ptr;//possible bcz you are trying to change address which is possible // *(ptr)++;//possible // *(++ptr);//possible // ++*ptr;//not possible bcz you trying to change value which is not allowed // (*ptr)++;//not possible // ++(*ptr);//not possible printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr); } 

Intenta analizar el resultado del código anterior

2) int const * p : se llama ‘ **constant pointe**r ‘, es decir, la address is constant but value is not constant . Aquí no tiene permitido cambiar la dirección pero puede modificar el valor.

Nota : el puntero constante (arriba del caso) debe inicializarse mientras se declara.

Para entender esto, verifique el código simple.

 #include int main() { int x = 300; int* const p; p = &x; printf("x = %dp =%p and *p = %d\n",num,p,*p); } 

En el código anterior, si observa que no hay ++ * p o * p ++ Entonces, puede pensar que este es un caso simple porque no estamos cambiando la dirección o el valor, pero producirá un error. Por qué ? Motivo que menciono en los comentarios.

 #include int main() { int x = 300; /** constant pointer must initialize while decaring itself **/ int* const p;//constant pointer ie its pointing to some address(here its pointing to garbage), it should point to same address(ie garbage ad dress only p = &x;// but here what we are doing ? we are changing address. we are making p to point to address of x instead of garbage address. printf("x = %dp =%p and *p = %d\n",num,p,*p); } 

Entonces, ¿cuál es la solución de este problema?

  int* const p = &x; 

Para obtener más información sobre este caso, consideremos el siguiente ejemplo.

 #include int main() { int num = 300; int *const ptr = #//constant value, address is modifible printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr); *ptr++;//not possible // *++ptr;//not possible bcz you are trying to change address which is not possible // *(ptr)++;//not possible // *(++ptr);//not possible // ++*ptr;// possible bcz you trying to change value which is allowed // (*ptr)++;// possible // ++(*ptr);// possible printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr); } 

3) const int * const p : Aquí tanto la dirección como el valor son constantes .

Para entender esto, verifique el código a continuación

 #include int main() { int num = 300; const int* const ptr = #//constant value,constant address printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr); *ptr++;//not possible ++*ptr;//not possible printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr); } 
 const char *p = "Hello"; *p means "Hello" ^ | p *p++ means "Hello" ^ | p *++p means "Hello" ^ | (WHILE THE STATEMENT IS EXECUTED) p *++p means "Hello" ^ | (AFTER THE STATEMENT IS EXECUTED) p 

++*p significa que está tratando de incrementar el valor ASCII de *p que

  is "Hello" ^ | p 

no puede incrementar el valor porque es una constante por lo que obtendría un error

en cuanto a su ciclo while, el ciclo se ejecuta hasta que *p++ llegue al final de la cadena donde hay un carácter '\0' (NULO).

Ahora bien, dado que *p++ omite el primer carácter, solo obtendrá su salida a partir del segundo carácter.

El siguiente código no generará nada porque while loop tiene '\0'

 const char *p = "Hello"; while('\0') printf("%c",*p); 

El siguiente código le dará el mismo resultado que el próximo código, es decir, ello.

 const char *p = "Hello"; while(*++p) printf("%c",*p); 

……………………………..

 const char *p = "Hello"; while(*p++) printf("%c",*p);