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 pcomo 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; pnos diz onde na memória há algum espaço reservado para armazenar a char.
A instrução também inicializa ppara apontar para o primeiro caractere na cadeia literal "Hello". Para o exercício deste exercício, é importante entender pcomo 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 iantes 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 intcó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, iserá 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. pfoi 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 ++ptem 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 ptrserá 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 ++ptrpeç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 *ptrpeç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 *pparte 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 charcom 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 *pparte é 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 ppontos 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, ptrna verdade, for um identificador de matriz. O terceiro e o quarto travam se ptrapontarem 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++)