Recientemente me he encontrado con este problema que no puedo entender por mí mismo.
¿Qué significan realmente estas tres expresiones ?
*ptr++
*++ptr
++*ptr
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 señalado. También puedo suponer que puede haber muchas cosas sobre la precedencia y el orden de evaluación. Al igual que uno incrementa el puntero primero, luego obtiene el contenido de ese puntero, uno simplemente recupera el contenido y luego incrementa el puntero, etc. etc. Como puede ver, no tengo una comprensión clara sobre sus operaciones reales , lo que me gustaría despejar lo antes posible. Pero estoy realmente perdido cuando tengo la oportunidad de aplicarlos en los programas. 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 se imprimiría Hello
. Una solicitud final: déme 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.
(*ptr)++
(paréntesis necesarios para desambiguar*ptr++
)char* p
, señalando una cadena terminada válida de caracteres únicos. Luego tenga una funciónfn(char ch)
que imprima tanto elch
parámetro como el carácter actual señalado porp
. Ahora invoquefn(*p++);
P: ¿fn
Imprime el mismo personaje dos veces ? Te sorprendería cuántos profesores se equivocan con esa pregunta.const char* p = "Hello";
Respuestas:
Aquí hay una explicación detallada que espero sea útil. Comencemos con su programa, ya que es el más simple de explicar.
La primera declaración:
declara
p
como puntero achar
. Cuando decimos "puntero a unchar
", ¿qué significa eso? Significa que el valor dep
es la dirección de achar
;p
nos dice dónde en la memoria hay un espacio reservado para guardar achar
.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 entenderp
que no apunta a toda la cadena, sino solo al primer carácter'H'
,. Después de todo,p
es un puntero a unochar
, no a toda la cadena. El valor dep
es la dirección de la'H'
en"Hello"
.Luego configuras un bucle:
¿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):++
e indirection*
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 lap++
parte. Así que tomemos lap++
parte primero.2. Valor de expresión de postfix . El valor de
p++
es el valor dep
antes del incremento . Si usted tiene:la salida será:
porque
i++
evalúai
antes del incremento. Del mismo modop++
se va a evaluar el valor actual dep
. Como sabemos, el valor actual dep
es la dirección de'H'
.Entonces ahora la
p++
parte de*p++
ha sido evaluada; que es el valor actual dep
. Entonces*
sucede la parte.*(current value of p)
significa: acceder al valor en la dirección que poseep
. 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 eseint
código nuevamente:Como se señaló anteriormente, el resultado será:
Cuando
i++
se evalúa en el primeroprintf()
, se evalúa a 7. Pero el estándar C garantiza que en algún momento antes de que el segundoprintf()
comience a ejecutarse, el efecto secundario del++
operador habrá tenido lugar. Es decir, antes de queprintf()
ocurra lo segundo ,i
se habrá incrementado como resultado del++
operador en lo primeroprintf()
. 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: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: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 comoMucho para eso. ¿Qué pasa con el resto? Usted pregunta sobre el significado de estos:
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 comolo 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:
El resultado será:
... diferente de lo que vimos con el operador postfix. Del mismo modo, si tiene:
la salida será:
¿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:
El resultado sorprendentemente egoísta será:
¡¿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 comochar
, y se obtiene elchar
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:
Ese también es interesante. Si usted tiene:
te dará esta salida entusiasta:
¿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 primeroprintf()
se va a imprimir'H'
. Luego, como efecto secundario ,'H'
se incrementará a'I'
. El segundoprintf()
imprime eso'I'
. Y tienes tu alegre saludo.De acuerdo, pero en esos dos últimos casos, ¿por qué necesito
¿Por qué no puedo tener algo como
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
Por qué no? Porque en este caso,
p
es una matriz. Una matriz no es un valor l modificable; no puede cambiar losp
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:
Y aquí hay un cuarto, tan divertido como los otros tres:
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 siptr
apunta a una cadena literal.Ahí tienes. Espero que todo sea cristal ahora. Has sido una gran audiencia, y estaré aquí toda la semana.
fuente
Supongamos que
ptr
apunta al elemento i-ésimo de la matrizarr
.*ptr++
evalúaarr[i]
y estableceptr
para señalar el elemento (i + 1) -th dearr
. Es equivalente a*(ptr++)
.*++ptr
estableceptr
que apunta al elemento (i + 1) -th dearr
y evalúa aarr[i+1]
. Es equivalente a*(++ptr)
.++*ptr
aumentaarr[i]
en uno y evalúa su valor aumentado; el punteroptr
se deja intacto. Es equivalente a++(*ptr)
.También hay uno más, pero necesitarías paréntesis para escribirlo:
(*ptr)++
aumentaarr[i]
en uno y evalúa su valor antes de ser aumentado; el punteroptr
se deja nuevamente intacto.El resto lo puedes resolver tú mismo; También fue respondido por @Jaguar.
fuente
*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 posterior.
Esto dará
Hello
como salidafuente
Hello
La condición en tu circuito es mala:
Es lo mismo que
Y eso está mal, esto debería ser:
*ptr++
es lo mismo que*(ptr++)
, que es:*++ptr
es lo mismo que*(++ptr)
, que es:++*ptr
es lo mismo que++(*ptr)
, que es:fuente
Tiene razón sobre la precedencia, tenga en cuenta que
*
tiene precedencia sobre el incremento de prefijo, pero no sobre el incremento de postfix. Así es como se desglosan:*ptr++
- ir de izquierda a derecha, desreferenciar el puntero y luego incrementar el valor del puntero (no a lo que apunta, debido a la precedencia de postfix sobre la desreferencia)*++ptr
- incremente el puntero y luego desreferenciarlo, esto se debe a que el prefijo y la desreferencia tienen la misma precedencia, por lo que se evalúan en orden de derecha a izquierda++*ptr
- similar a lo anterior en términos de precedencia, nuevamente yendo de derecha a izquierda para desreferenciar el puntero y luego incrementar lo que apunta el puntero. Tenga en cuenta que en su caso, este conducirá a un comportamiento indefinido porque está intentando modificar una variable de solo lectura (char* p = "Hello";
).fuente
Voy a agregar mi opinión porque si bien las otras respuestas son correctas, creo que les falta algo.
medio
Donde como
medio
Es importante entender que el incremento posterior (y el decremento posterior) significan
¿Por qué eso importa? Bueno, en C eso no es tan importante. Sin embargo, en C ++
ptr
podría ser un tipo complejo como un iterador. Por ejemploEn este caso, porque
it
es un tipo complejoit++
puede tener efectos secundarios debido a latemp
creación. Por supuesto, si tiene suerte, el compilador intentará desechar el código que no es necesario, pero si el constructor o destructor del iterador hace algo,it++
mostrará esos efectos cuando creetemp
.Lo corto de lo que intento decir es Escribe lo que quieres decir . Si quiere decir increment ptr, entonces
++ptr
no escribaptr++
. Si te refieres,temp = ptr, ptr += 1, temp
entonces escribeptr++
fuente
Esto es lo mismo que:
Entonces
ptr
se recupera el valor del objeto señalado por , luegoptr
se incrementa.Esto es lo mismo que:
Entonces, el puntero
ptr
se incrementa, luegoptr
se lee el objeto señalado por .Esto es lo mismo que:
Entonces el objeto señalado por
ptr
se incrementa;ptr
en sí no ha cambiado.fuente
postfix y prefix tienen mayor prioridad que la desreferencia
* ptr ++ aquí publica increment ptr y luego apunta al nuevo valor de ptr
* ++ ptr aquí Pre Increment puño luego apuntando a un nuevo valor de ptr
++ * ptr aquí primero obtiene el valor de ptr apuntando e incrementa esa vlaue
fuente
Expresiones de puntero: * ptr ++, * ++ ptr y ++ * ptr:
Nota : los punteros deben inicializarse y deben tener una dirección válida. Porque en la RAM, aparte de nuestro programa (a.out), hay muchos más programas ejecutándose simultáneamente, es decir, si intenta acceder a alguna memoria que no estaba reservada para su sistema operativo, se producirá un error de segmentación.
Antes de explicar esto, consideremos un ejemplo simple.
analice la salida del código anterior, espero que haya obtenido la salida del código anterior. Una cosa está clara en el código anterior es que el nombre del puntero ( ptr ) significa que estamos hablando de dirección y * ptr significa que estamos hablando de valor / datos.
CASO 1 : * ptr ++, * ++ ptr, * (ptr ++) y * (++ ptr):
Las 4 sintaxis mencionadas anteriormente son similares,
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 la expresión, luego descubra las prioridades del operador. Si varios operadores tienen la misma prioridad, compruebo el orden de evolución o asociatividad que puede ser de derecha (R) a izquierda (L) o de izquierda a derecha.
* ptr ++ : Aquí hay 2 operadores, a saber, desreferenciación (*) e ++ (incremento). Ambos tienen la misma prioridad, luego verifique la asociatividad que es de R a L. Así que comienza a resolver de derecha a izquierda, cualesquiera operadores que vengan primero.
* ptr ++ : first ++ llegó al resolver de R a L, por lo que la dirección se incrementa pero su incremento posterior.
* ++ ptr : Igual que el primero aquí, la dirección también se incrementa pero su incremento previo.
* (ptr ++) : Aquí hay 3 operadores, entre ellos el agrupamiento () que tiene la más alta prioridad, así que primero se resuelve ptr ++, es decir, la dirección se incrementa pero se publica.
* (++ ptr) : Igual que el caso anterior, aquí también la dirección se incrementa pero se incrementa previamente.
CASO 2 : ++ * ptr, ++ (* ptr), (* ptr) ++:
Las 4 sintaxis anteriores son similares, en todos los valores / datos se incrementan, pero la forma en que se cambia el valor es diferente.
++ * ptr : first * llegó al resolver de R a L, por lo que el valor se cambia pero su incremento previo.
++ (* ptr) : Igual que el caso anterior, el valor se modifica.
(* ptr) ++ : Aquí hay 3 operadores, entre ellos el agrupamiento () que tiene la máxima prioridad, Inside () * ptr está allí, así que primero * ptr se resuelve, es decir, el valor se incrementa pero se publica.
Nota : ++ * ptr y * ptr = * ptr + 1 ambos son iguales, en ambos casos el valor cambia. ++ * ptr: solo se usa 1 instrucción (INC), el valor directamente se cambia en un solo disparo. * ptr = * ptr + 1: aquí el primer valor se incrementa (INC) y luego se asigna (MOV).
Para comprender todas las diferentes sintaxis de incremento anteriores en el puntero, consideremos un código simple:
En el código anterior, intente comentar / descomentar comentarios y analizar resultados.
Punteros como constantes : no hay maneras en que pueda hacer que los punteros sean constantes, pocos los menciono aquí.
1) const int * p OR int const * p : Aquí
value
es constante , la dirección no es constante , es decir, ¿hacia dónde apunta p? Alguna dirección? En esa dirección, ¿cuál es el valor? Algún valor ¿verdad? Ese valor es constante, no puede modificar ese valor, pero ¿dónde apunta el puntero? Alguna direccion correcta? Puede apuntar a otra dirección también.Para entender esto, consideremos el siguiente código:
Intenta analizar la salida del código anterior
2) int const * p : se llama '
**constant pointe**r
' es deciraddress is constant but value is not constant
. Aquí no puede cambiar la dirección, pero puede modificar el valor.Nota : el puntero constante (caso anterior) debe inicializarse mientras se declara.
Para entender esto, verifiquemos el código simple.
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é ? Razón que menciono en los comentarios.
Entonces, ¿cuál es la solución de este problema?
para obtener más información sobre este caso, consideremos el siguiente ejemplo.
3) const int * const p : Aquí tanto la dirección como el valor son constantes .
Para entender esto, verifiquemos el siguiente código
fuente
++*p
significa que está intentando incrementar el valor ASCII del*p
cualno 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
*p++
llegar al final de la cadena donde hay un'\0'
(NULO).Ahora desde
*p++
omite el primer carácter, solo obtendrá su salida a partir del segundo carácter.El siguiente código no generará nada porque mientras el bucle tiene
'\0'
El siguiente código le dará la misma salida que el siguiente código, es decir, ello.
...................................
fuente