Aqui está uma explicação detalhada que, espero, será útil. Vamos começar com o seu programa, pois é o mais simples de explicar.
int main()
{
const char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
A primeira afirmação:
const char* p = "Hello";
declara p
como um ponteiro para char
. Quando dizemos "ponteiro para a char
", o que isso significa? Isso significa que o valor de p
é o endereço de a char
; p
nos diz onde na memória há algum espaço reservado para armazenar a char
.
A instrução também inicializa p
para apontar para o primeiro caractere na cadeia literal "Hello"
. Para o exercício deste exercício, é importante entender p
como apontar não para toda a cadeia, mas apenas para o primeiro caractere 'H'
. Afinal, p
é um ponteiro para um char
, não para toda a string. O valor de p
é o endereço do 'H'
in "Hello"
.
Então você configura um loop:
while (*p++)
O que significa a condição do loop *p++
? Aqui estão três coisas que tornam isso intrigante (pelo menos até que a familiaridade se manifeste):
- A precedência dos dois operadores, postfix
++
e indirection*
- O valor de uma expressão de incremento postfix
- O efeito colateral de uma expressão de incremento postfix
1. Precedência . Uma rápida olhada na tabela de precedência para os operadores informará que o incremento do postfix tem uma precedência mais alta (16) do que a desreferência / indireção (15). Isto significa que a expressão complexa *p++
vai ser agrupados como: *(p++)
. Ou seja, a *
peça será aplicada ao valor da p++
peça. Então, vamos p++
participar primeiro.
2. Valor da expressão Postfix . O valor de p++
é o valor de p
antes do incremento . Se você tem:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
a saída será:
7
8
porque i++
avalia i
antes do incremento. Da mesma forma, p++
vai avaliar o valor atual de p
. Como sabemos, o valor atual de p
é o endereço de 'H'
.
Então agora a p++
parte de *p++
foi avaliada; é o valor atual de p
. Então a *
parte acontece. *(current value of p)
significa: acessar o valor no endereço mantido por p
. Sabemos que o valor nesse endereço é 'H'
. Portanto, a expressão é *p++
avaliada como 'H'
.
Agora espere um minuto, você está dizendo. Se for *p++
avaliado 'H'
, por que isso não é 'H'
impresso no código acima? É aí que entram os efeitos colaterais .
3. Efeitos colaterais da expressão do Postfix . O postfix ++
tem o valor do operando atual, mas tem o efeito colateral de incrementar esse operando. Hã? Dê uma olhada nesse int
código novamente:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
Como observado anteriormente, a saída será:
7
8
Quando i++
é avaliado no primeiro printf()
, ele avalia como 7. Mas o padrão C garante que, em algum momento antes do printf()
início da execução do segundo , o efeito colateral do ++
operador tenha ocorrido. Ou seja, antes que o segundo printf()
aconteça, i
será incrementado como resultado do ++
operador no primeiro printf()
. A propósito, essa é uma das poucas garantias que o padrão oferece sobre o momento dos efeitos colaterais.
No seu código, então, quando a expressão *p++
é avaliada, ela é avaliada como 'H'
. Mas quando você chegar a isso:
printf ("%c", *p)
esse efeito colateral irritante ocorreu. p
foi incrementado. Uau! Não aponta mais para 'H'
, mas para o passado de um personagem 'H'
: para o 'e'
, em outras palavras. Isso explica sua saída do cockneyfied:
ello
Daí o coro de sugestões úteis (e precisas) nas outras respostas: para imprimir a pronúncia recebida "Hello"
e não sua contraparte cockney, você precisa de algo como
while (*p)
printf ("%c", *p++);
Tanto para esse. E o resto? Você pergunta sobre o significado deles:
*ptr++
*++ptr
++*ptr
Acabamos de falar sobre o primeiro, então vamos olhar para o segundo: *++ptr
.
Vimos em nossa explicação anterior que o incremento do postfix p++
tem uma certa precedência , um valor e um efeito colateral . O incremento do prefixo ++p
tem o mesmo efeito colateral da contraparte do postfix: incrementa seu operando em 1. No entanto, possui uma precedência diferente e um valor diferente .
O incremento do prefixo tem precedência menor que o postfix; ele tem precedência 15. Em outras palavras, ele tem a mesma precedência que o operador de desreferência / indireção *
. Em uma expressão como
*++ptr
o que importa não é precedência: os dois operadores são idênticos em precedência. Assim, a associatividade entra em ação. O incremento do prefixo e o operador indireto têm associatividade direita-esquerda. Por causa dessa associatividade, o operando ptr
será agrupado com o operador mais à direita ++
antes do operador mais à esquerda *
. Em outras palavras, a expressão será agrupada *(++ptr)
. Portanto, como *ptr++
por um motivo diferente, aqui também a *
peça será aplicada ao valor da ++ptr
peça.
Então, qual é esse valor? O valor da expressão de incremento do prefixo é o valor do operando após o incremento . Isso o torna um animal muito diferente do operador de incremento postfix. Digamos que você tenha:
int i = 7;
printf ("%d\n", ++i);
printf ("%d\n", i);
A saída será:
8
8
... diferente do que vimos com o operador postfix. Da mesma forma, se você tiver:
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++
a saída será:
H e e l // good dog
Você vê o porquê?
Agora chegamos à terceira expressão que você perguntou ++*ptr
,. Esse é o mais difícil de todos, na verdade. Ambos os operadores têm a mesma precedência e associatividade direita-esquerda. Isso significa que a expressão será agrupada ++(*ptr)
. A ++
peça será aplicada ao valor da *ptr
peça.
Então, se tivermos:
char q[] = "Hello";
char* p = q;
printf ("%c", ++*p);
o resultado surpreendentemente egoísta será:
I
O que?! Ok, então a *p
parte vai avaliar 'H'
. Então o ++
entra em jogo; nesse ponto, ele será aplicado ao 'H'
, e não ao ponteiro! O que acontece quando você adiciona 1 a 'H'
? Você recebe 1 mais o valor ASCII de 'H'
72; você começa 73. declara que como char
, e você começa a char
com o valor ASCII de 73: 'I'
.
Isso cuida das três expressões que você perguntou na sua pergunta. Aqui está outro, mencionado no primeiro comentário à sua pergunta:
(*ptr)++
Esse também é interessante. Se você tem:
char q[] = "Hello";
char* p = q;
printf ("%c", (*p)++);
printf ("%c\n", *p);
isso lhe dará uma saída entusiasmada:
HI
O que está acontecendo? Novamente, é uma questão de precedência , valor da expressão e efeitos colaterais . Por causa dos parênteses, a *p
parte é tratada como uma expressão primária. Expressões primárias superam todo o resto; eles são avaliados primeiro. E *p
, como você sabe, avalia como 'H'
. O restante da expressão, a ++
parte, é aplicada a esse valor. Então, neste caso, (*p)++
torna-se 'H'++
.
Qual é o valor de 'H'++
? Se você disse 'I'
que esqueceu (já!) Nossa discussão sobre valor versus efeito colateral com incremento do postfix. Lembre-se, 'H'++
avalia o valor atual de 'H'
. Então, o primeiro printf()
será impresso 'H'
. Então, como efeito colateral , isso 'H'
será incrementado para 'I'
. O segundo printf()
imprime isso 'I'
. E você tem sua alegre saudação.
Tudo bem, mas nesses dois últimos casos, por que eu preciso
char q[] = "Hello";
char* p = q;
Por que não posso simplesmente ter algo como
/*const*/ char* p = "Hello";
printf ("%c", ++*p); // attempting to change string literal!
Porque "Hello"
é uma string literal. Se você tentar ++*p
, você está tentando alterar 'H'
a string para 'I'
, tornando a string inteira "Iello"
. Em C, os literais de seqüência de caracteres são somente leitura; tentar modificá-los invoca um comportamento indefinido. "Iello"
também não está definido em inglês, mas isso é apenas coincidência.
Por outro lado, você não pode ter
char p[] = "Hello";
printf ("%c", *++p); // attempting to modify value of array identifier!
Por que não? Porque, neste caso, p
é uma matriz. Uma matriz não é um valor l modificável; você não pode alterar onde os p
pontos são pré ou pós-incremento ou decremento, porque o nome da matriz funciona como se fosse um ponteiro constante. (Isso não é o que realmente é; é apenas uma maneira conveniente de ver isso.)
Para resumir, aqui estão as três coisas que você perguntou:
*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
E aqui está um quarto, tão divertido quanto os outros três:
(*ptr)++ // effectively forces a dereference, then increments dereferenced value
O primeiro e o segundo travarão se, ptr
na verdade, for um identificador de matriz. O terceiro e o quarto travam se ptr
apontarem para uma string literal.
Aí está. Espero que esteja tudo cristal agora. Você tem sido um ótimo público, e eu estarei aqui a semana toda.
(*ptr)++
(parênteses necessário para disambiguate de*ptr++
)