Disclaimer : Conheço perfeitamente a semântica do incremento de prefixo e postfix. Então, por favor, não me explique como eles funcionam.
Lendo perguntas sobre estouro de pilha, não posso deixar de notar que os programadores ficam confusos com o operador de incremento do postfix repetidas vezes. A partir disso, surge a seguinte pergunta: existe algum caso de uso em que o incremento do postfix fornece um benefício real em termos de qualidade do código?
Deixe-me esclarecer minha pergunta com um exemplo. Aqui está uma implementação super concisa de strcpy
:
while (*dst++ = *src++);
Mas esse não é exatamente o código mais auto-documentado do meu livro (e produz dois avisos irritantes sobre os compiladores sãos). Então, o que há de errado com a seguinte alternativa?
while (*dst = *src)
{
++src;
++dst;
}
Podemos então nos livrar da tarefa confusa na condição e obter um código completamente livre de aviso:
while (*src != '\0')
{
*dst = *src;
++src;
++dst;
}
*dst = '\0';
(Sim, eu sei, src
e dst
terá valores finais diferentes nessas soluções alternativas, mas, como strcpy
retorna imediatamente após o loop, isso não importa nesse caso.)
Parece que o objetivo do incremento do postfix é tornar o código o mais conciso possível. Simplesmente deixo de ver como isso é algo pelo qual devemos nos esforçar. Se isso era originalmente sobre desempenho, ainda é relevante hoje?
strcpy
método dessa maneira (por razões que você já mencionou).int c = 0; c++;
Respostas:
Embora tenha tido algumas implicações no desempenho, acho que o verdadeiro motivo é para expressar sua intenção de maneira limpa. A verdadeira questão é se algo
while (*d++=*s++);
expressa intenção claramente ou não. Na OMI, é verdade, e acho as alternativas que você oferece menos claras - mas isso pode (facilmente) ser o resultado de passar décadas se acostumando com a maneira como as coisas são feitas. Tendo aprendido C de K & R (porque não eram quase nenhum outros livros sobre C no momento), provavelmente também ajuda.Até certo ponto, é verdade que a dispersão foi avaliada em um grau muito maior no código antigo. Pessoalmente, acho que isso foi em grande parte uma coisa boa - entender algumas linhas de código geralmente é bastante trivial; o difícil é entender grandes pedaços de código. Testes e estudos mostraram repetidamente que o ajuste de todo o código na tela ao mesmo tempo é um fator importante para a compreensão do código. À medida que as telas se expandem, isso parece permanecer verdadeiro, mantendo o código (razoavelmente) conciso permanece valioso.
Claro que é possível exagerar, mas acho que não. Especificamente, acho que está exagerando quando entender uma única linha de código se torna extremamente difícil ou demorado - especificamente, quando entender menos linhas de código consome mais esforço do que entender mais linhas. Isso é frequente no Lisp e no APL, mas não parece (pelo menos para mim) o caso aqui.
Estou menos preocupado com os avisos do compilador - é minha experiência que muitos compiladores emitem avisos absolutamente ridículos regularmente. Embora eu certamente ache que as pessoas devem entender seu código (e quaisquer avisos que ele possa produzir), um código decente que acione um aviso em algum compilador não está necessariamente errado. É certo que os iniciantes nem sempre sabem o que podem ignorar com segurança, mas não permanecemos iniciantes para sempre e não precisamos codificar como somos.
fonte
if(x = y)
, eu juro!), Você deve ajustar as opções de aviso do seu compilador para não perder acidentalmente o avisos que você faz como.-Wno-X
, mas se você deseja apenas fazer uma exceção a ele e aplicar o aviso em qualquer outro lugar, isso é muito mais compreensível do que usar saltos arcanos como Chris se refere a .(void)x
silêncio de avisos não utilizados são expressões bastante conhecidas para silenciar avisos de outra forma úteis. A anotação parece um pouco compragmas
, não portátil na melhor das hipóteses: /É uma coisa de hardware
Interessante que você notaria. O incremento do Postfix provavelmente está lá por várias razões perfeitamente boas, mas, como muitas coisas em C, sua popularidade pode ser atribuída à origem do idioma.
Embora o C tenha sido desenvolvido em uma variedade de máquinas antigas e com pouca potência, o C e o Unix foram os primeiros a se destacar nos modelos gerenciados por memória do PDP-11. Estes eram computadores relativamente importantes em seus dias e o Unix era muito melhor - exponencialmente melhor - do que os outros 7 sistemas operacionais ruins disponíveis para o -11.
E acontece que no PDP-11,
e
... foram implementados no hardware como modos de endereçamento. (Além disso
*p
, mas nenhuma outra combinação.) Nessas máquinas antigas, todas com menos de 0,001 GHz, salvar uma instrução ou duas em um loop quase deve ter sido uma espera por segundo ou espera por um minuto ou saída por diferença de almoço. Isso não diz exatamente o pós-incremento, mas um loop com pós-incremento de ponteiro poderia ter sido muito melhor do que indexar na época.Como conseqüência, os padrões de design são os idiomas C que se tornaram macros C mentais.
É algo como declarar variáveis logo após um
{
... não desde que o C89 estava atual, isso foi um requisito, mas agora é um padrão de código.Atualização: Obviamente, o principal motivo
*p++
está no idioma, porque é exatamente o que se quer fazer com tanta frequência. A popularidade do padrão de código foi reforçada pelo hardware popular que surgiu e correspondia ao padrão já existente de uma linguagem projetada um pouco antes da chegada do PDP-11.Hoje em dia, não faz diferença qual padrão você usa, ou se você usa a indexação, e geralmente programamos em um nível mais alto de qualquer maneira, mas deve ter sido muito importante nessas máquinas de 0,001 GHz e usar algo diferente
*--x
ou*x++
significaria que você não "pegou" o PDP-11, e você pode ter pessoas chegando até você e dizendo "você sabia disso ..." :-) :-)fonte
−−i
ei++
em C. Sei
ej
ambas eram variáveis de registro, uma expressão que*(−−i) = *(j++)
poderia ser compilada em uma única instrução de máquina. [...] Dennis Ritchie contradiz inequivocamente esse mito popular. [O desenvolvimento da linguagem C ] "O prefixo, o postfix
--
e os++
operadores foram introduzidos na linguagem B (antecessora de C) por Ken Thompson - e não, não foram inspirados pelo PDP-11, que não existia na época.Citando "O Desenvolvimento da Linguagem C", de Dennis Ritchie :
fonte
A razão óbvia para o operador pós-incremento de existir é para que você não tem que expressões escrever como
(++x,x-1)
ou(x+=1,x-1)
todo o lugar ou declarações triviais inutilmente separadas com fácil de entender os efeitos colaterais fora em várias instruções. aqui estão alguns exemplos:Sempre que ler ou escrever uma sequência na direção direta, pós-incremento, e não pré-incremento, é quase sempre a operação natural. Quase nunca encontrei usos no mundo real para pré-incremento, e o único uso principal que encontrei para o predecrement é converter números em strings (porque os sistemas de escrita humana escrevem números de trás para a frente).
Edit: Na verdade, não seria tão feio; em vez de
(++x,x-1)
você poderia, é claro, usar++x-1
ou(x+=1)-1
. Aindax++
é mais legível.fonte
(++x,x-1)
(chamada de função, talvez?)x++
da maioria dos casos (uma exceção:x
é um tipo não assinado com classificação menor queint
e o valor inicial dex
é o valor máximo desse tipo).O PDP-11 ofereceu operações de pós-incremento e pré-decremento no conjunto de instruções. Agora, essas não eram instruções. Eles foram modificadores de instruções que permitiram especificar que você desejava o valor de um registro antes que ele fosse incrementado ou depois que ele fosse diminuído.
Aqui está uma etapa de cópia de texto no idioma da máquina:
que movia uma palavra de um local para outro, apontada pelos registros, e os registros estariam prontos para fazê-lo novamente.
Como o C foi desenvolvido no PDP-11, muitos conceitos úteis chegaram ao C. O objetivo era ser um substituto útil para a linguagem assembler. Pré-incremento e pós-decremento foram adicionados para simetria.
fonte
Duvido que tenha sido realmente necessário. Tanto quanto eu sei, ele não compila em nada mais compacto na maioria das plataformas do que usar o pré-incremento como você fez, no loop. Só que, no momento em que foi criado, a dispersão do código era mais importante do que a clareza do código.
Isso não quer dizer que a clareza do código não é (ou não era) importante, mas quando você estava digitando em um modem de baixa velocidade de transmissão (qualquer coisa que você mede na velocidade de transmissão é lenta) em que cada pressionamento de tecla precisava o mainframe e, em seguida, receba eco de um byte de cada vez com um único bit usado como verificação de paridade, você não precisa digitar muito.
É como o & e | operador com precedência menor que ==
Ele foi projetado com as melhores intenções (uma versão sem curto-circuito de && e ||), mas agora confunde programadores todos os dias e provavelmente nunca mudará.
De qualquer forma, esta é apenas uma resposta, pois acho que não há uma boa resposta para sua pergunta, mas provavelmente me provarei errado por um programador de guru mais do que eu.
--EDITAR--
Devo observar que acho que ter os dois é incrivelmente útil, apenas estou apontando que não mudará se alguém gosta ou não.
fonte
Eu gosto do aperitivo postfix ao lidar com ponteiros (independentemente de eu estar desreferenciando-os) porque
lê mais naturalmente como "ir para o próximo local" do que o equivalente
o que certamente deve confundir os iniciantes na primeira vez em que o veem usado com um ponteiro em que sizeof (* p)! = 1.
Tem certeza de que está afirmando o problema de confusão corretamente? Não é o que confunde os iniciantes o fato de o operador postfix ++ ter uma precedência mais alta que o operador dereference *, para que
analisa como
e não
como alguns podem esperar?
(Você acha que isso é ruim? Consulte Ler declarações C ).
fonte
Entre os elementos sutis da boa programação estão a localização e o minimalismo :
No mesmo espírito,
x++
pode ser visto como uma maneira de localizar uma referência ao valor atualx
, indicando imediatamente que você - pelo menos por enquanto - terminou com esse valor e deseja ir para o próximo (válido sex
é umint
ou um ponteiro ou iterador). Com um pouco de imaginação, você pode comparar isso a deixar o antigo valor / posiçãox
"fora do escopo" imediatamente depois que ele não é mais necessário, e passar para o novo valor. O ponto exato em que essa transição é possível é destacado pelo postfix++
. A implicação de que esse é provavelmente o uso final do "antigo"x
pode ser uma visão valiosa do algoritmo, ajudando o programador a varrer o código ao redor. Claro, o postfix++
pode colocar o programador à procura de usos do novo valor, o que pode ou não ser bom dependendo de quando esse novo valor é realmente necessário. Portanto, é um aspecto da "arte" ou "arte" da programação para determinar o que é mais importante. útil nas circunstâncias.Embora muitos iniciantes possam ficar confusos com um recurso, vale a pena comparar isso com os benefícios de longo prazo: iniciantes não permanecem iniciantes por muito tempo.
fonte
Uau, muitas respostas não são muito objetivas (se é que posso ser tão ousado) e, por favor, perdoe-me se estou apontando o óbvio - principalmente à luz do seu comentário, para não apontar a semântica, mas o óbvio (da perspectiva de Stroustrup, suponho) ainda não parece ter sido publicado! :)
O Postfix
x++
produz um temporário que é passado 'para cima' na expressão, enquanto a variávelx
é posteriormente incrementada.O prefixo
++x
não produz um objeto temporário, mas incrementa 'x' e passa o resultado para a expressão.O valor é a conveniência, para quem conhece o operador.
Por exemplo:
Obviamente, os resultados deste exemplo podem ser produzidos a partir de outro código equivalente (e talvez menos oblíquo).
Então, por que temos o operador postfix? Eu acho que porque é um idioma que persiste, apesar da confusão que obviamente cria. É uma construção legada que já foi percebida como tendo valor, embora não tenha certeza de que o valor percebido foi tanto para desempenho quanto para conveniência. Essa conveniência não foi perdida, mas acho que a apreciação da legibilidade aumentou, resultando no questionamento do objetivo do operador.
Precisamos mais do operador postfix? Provavelmente não. Seu resultado é confuso e cria uma barreira à compreensibilidade. Certos bons codificadores certamente saberão imediatamente onde achar que é útil, geralmente em lugares onde ela possui uma beleza peculiarmente "do tipo PERL". O custo dessa beleza é legibilidade.
Concordo que o código explícito traz benefícios sobre a discrepância, legibilidade, compreensão e manutenção. Bons designers e gerentes querem isso.
No entanto, há uma certa beleza que alguns programadores reconhecem que os incentiva a produzir código com itens puramente para simplificar a beleza - como o operador postfix - que código eles nunca teriam pensado - ou acharam intelectualmente estimulantes. Existe algo que só o torna mais bonito, se não mais desejável, apesar da expressividade.
Em outras palavras, algumas pessoas
while (*dst++ = *src++);
simplesmente consideram uma solução mais bonita, algo que tira o fôlego com sua simplicidade, tanto quanto se fosse um pincel na tela. O fato de você ser forçado a entender o idioma para apreciar a beleza apenas aumenta sua magnificência.fonte
for
declaração, até mesmowhile
(goto
afinal, afinal)?Vejamos a justificativa original de Kernighan & Ritchie (páginas 42 e 43 originais da K&R):
O texto continua com alguns exemplos que usam incrementos no índice, com o objetivo explícito de escrever código " mais compacto ". Portanto, a razão por trás desses operadores é a conveniência de um código mais compacto.
Os três exemplos dados (
squeeze()
,getline()
estrcat()
) usam apenas o postfix em expressões usando indexação. Os autores comparam o código com uma versão mais longa que não usa incrementos incorporados. Isso confirma que o foco está na compacidade.K&R destacam na página 102, o uso desses operadores em combinação com a desreferenciação de ponteiros (por exemplo,
*--p
e*p--
). Nenhum outro exemplo é dado, mas novamente, eles deixam claro que o benefício é a compacidade.O prefixo é, por exemplo, muito comumente usado ao decrementar um índice ou um ponteiro olhando para o final.
fonte
Vou discordar da premissa de que
++p
, dep++
alguma forma, é difícil de ler ou pouco claro. Um significa "incrementar p e depois ler p", o outro significa "ler p e depois incrementar p". Nos dois casos, o operador no código está exatamente onde está na explicação do código; portanto, se você sabe o que++
significa, sabe o que significa o código resultante.Você pode criar código ofuscado usando qualquer sintaxe, mas não vejo um caso aqui
p++
/ que++p
seja inerentemente ofuscante.fonte
isto é do ponto de vista de um engenheiro elétrico:
existem muitos processadores que possuem operadores integrados de pós-incremento e pré-decremento com o objetivo de manter uma pilha LIFO (último a entrar, primeiro a sair).
pode ser como:
Não sei, mas essa é a razão pela qual eu esperaria ver os operadores de incremento de prefixo e postfix.
fonte
Para enfatizar o ponto de vista de Christophe sobre compacidade, quero mostrar a vocês algum código da V6. Isso faz parte do compilador C original, em http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/source/c/c00.c :
Esse é um código que foi divulgado no samizdat como uma educação em bom estilo, e mesmo assim nunca o escreveríamos hoje. Veja quantos efeitos colaterais foram agrupados
if
ewhile
condições e , inversamente, como os doisfor
loops não têm uma expressão de incremento porque isso é feito no corpo do loop. Veja como os blocos são envoltos em chaves apenas quando absolutamente necessário.Parte disso foi certamente uma micro-otimização manual do tipo que esperamos que o compilador faça hoje, mas eu também proporia a hipótese de que, quando toda a sua interface com o computador for um único tty de vidro de 80x25, você deseja seu código para ser o mais denso possível, para que você possa ver mais ao mesmo tempo.
(O uso de buffers globais para tudo é provavelmente uma ressaca por cortar os dentes na linguagem assembly.)
fonte
rp->hflag =| xdflg
, o que acredito ser um erro de sintaxe em um compilador moderno. "Em algum momento" é precisamente V6, por acaso; a mudança para a+=
forma aconteceu na V7. (O compilador V6 única aceite=+
, se eu estou lendo este código corretamente - ver o símbolo () no mesmo arquivo.)Talvez você deva pensar também em cosméticos.
indiscutivelmente "parece melhor" do que
Por alguma razão, o operador pós-incremento parece muito atraente. É curto, doce e aumenta tudo em 1. Compare-o com o prefixo:
"olha para trás", não é? Você nunca vê um sinal de adição PRIMEIRO em matemática, exceto quando alguém é tolo ao especificar algo positivo (
+0
ou algo assim). Estamos acostumados a ver OBJECT + ALGOTem algo a ver com a classificação? A, A +, A ++? Posso dizer que fiquei muito chocado no começo que o pessoal do Python removeu
operator++
de sua linguagem!fonte
i++
retorna o valor originali
, ei+=1
e++i
retornar o valor originali
mais 1. Como você está usando os valores de retorno para o fluxo de controle, isso é importante.Contando para trás 9 a 0, inclusive:
Ou o equivalente while loop.
fonte
for (unsigned i = 9; i <= 9; --i)
?