Eu sou um programador autodidata. Comecei a programar cerca de 1,5 anos atrás. Agora comecei a ter aulas de programação na escola. Tivemos aulas de programação por 1/2 ano e agora temos outras 1/2.
Nas aulas, estamos aprendendo a programar em C ++ (que é uma linguagem que eu já sabia usar bastante antes de começarmos).
Não tive dificuldades durante esta aula, mas há um problema recorrente para o qual não consegui encontrar uma solução clara.
O problema é o seguinte (em Pseudocódigo):
do something
if something failed:
handle the error
try something (line 1) again
else:
we are done!
Aqui está um exemplo em C ++
O código solicita que o usuário insira um número e o faz até que a entrada seja válida. Ele usa cin.fail()
para verificar se a entrada é inválida. Quando cin.fail()
é que true
eu tenho que ligar cin.clear()
e cin.ignore()
poder continuar a receber informações do fluxo.
Estou ciente de que esse código não faz check-in
EOF
. Os programas que escrevemos não devem fazer isso.
Aqui está como eu escrevi o código em uma das minhas tarefas na escola:
for (;;) {
cout << ": ";
cin >> input;
if (cin.fail()) {
cin.clear();
cin.ignore(512, '\n');
continue;
}
break;
}
Meu professor disse foi que eu não deveria estar usando break
e continue
como esta. Ele sugeriu que eu deveria usar um regular while
ou do ... while
loop.
Parece-me que usar break
e continue
é a maneira mais simples de representar esse tipo de loop. Na verdade, eu pensei sobre isso por um bom tempo, mas não encontrei uma solução mais clara.
Eu acho que ele queria que eu fizesse algo assim:
do {
cout << ": ";
cin >> input;
bool fail = cin.fail();
if (fail) {
cin.clear();
cin.ignore(512, '\n');
}
} while (fail);
Para mim, esta versão parece muito mais complexa, pois agora também temos chamadas de variáveis fail
para acompanhar e a verificação de falha de entrada é feita duas vezes em vez de apenas uma vez.
Também imaginei que posso escrever o código dessa maneira (abusando da avaliação de curto-circuito):
do {
cout << ": ";
cin >> input;
if (fail) {
cin.clear();
cin.ignore(512, '\n');
}
} while (cin.fail() && (cin.clear(), cin.ignore(512, '\n', true);
Esta versão funciona exatamente como as outras. Ele não usa break
ou continue
e o cin.fail()
teste é feito apenas uma vez. Contudo, não me parece correto abusar da "regra de avaliação de curto-circuito" como esta. Eu também não acho que meu professor gostaria.
Esse problema não se aplica apenas à cin.fail()
verificação. Eu usei break
e continue
assim para muitos outros casos que envolvem a repetição de um conjunto de códigos até que uma condição seja atendida, onde algo também deve ser feito se a condição não for atendida (como chamar cin.clear()
e a cin.ignore(...)
partir do cin.fail()
exemplo).
Eu continuei usando break
e continue
ao longo do curso e agora meu professor parou de reclamar.
Quais são as suas opiniões sobre isso?
Você acha que meu professor está certo?
Você conhece uma maneira melhor de representar esse tipo de problema?
fonte
while (true)
e imediatamente supor que há umbreak
,return
outhrow
em algum lugar lá. A introdução de uma variável extra que precisa ser monitorada apenas para evitar umabreak
oucontinue
é contraproducente. Eu argumentaria que o problema com o código inicial é que o loop nunca termina uma iteração normalmente e que você precisa saber que há umcontinue
detalhe internoif
para saberbreak
que não será utilizado.dataIsValid
é mutável, para saber que o loop termina corretamente, preciso verificar se está definido quando os dados são válidos e também se não está desabilitado em nenhum momento posteriormente. Em um loop do formulário,do { ... if (!expr) { break; } ... } while (true);
eu sei que, se forexpr
avaliadofalse
, o loop será interrompido sem ter que olhar para o resto do corpo do loop.Respostas:
Eu escreveria a instrução if um pouco diferente, por isso é usada quando a entrada é bem-sucedida.
Também é mais curto.
O que sugere uma maneira mais curta que pode ser apreciada por seu professor:
fonte
break
no final. Isso por si só torna o código muito mais fácil de ler. Também não sabia que isso!(cin >> input)
era válido. Obrigado por me avisar!cout << ": ";
, violando o princípio DRY. Para o código do mundo real, isso seria inútil, uma vez que se torna propenso a erros (quando você precisar alterar essa parte posteriormente, precisará garantir que não se esqueça de alterar duas linhas da mesma maneira agora). Neverthelss dei-lhe um +1 para a sua primeira versão, que é um exemplo de como usá-lobreak
corretamente.cout << ": "
totalmente e nada quebraria.while
comando parece mais agradável".O que você precisa se esforçar é evitar loops crus .
Mova a lógica complexa para uma função auxiliar e, de repente, as coisas ficam muito mais claras:
fonte
getValidUserInput
é chamada muitas vezes e não apenas uma vez. Dou +1 para isso, mas aceitarei a resposta de Sjoerd, porque acho que ela responde melhor à minha pergunta.Não é tão
for(;;)
ruim assim. Não é tão claro quanto padrões como:Ou como Sjoerd colocou:
Vamos considerar o público principal desse material como sendo seus colegas programadores, incluindo a versão futura de si mesmo que não se lembra mais do porquê você parou o intervalo no final dessa maneira ou pulou o intervalo com a continuação. Este padrão do seu exemplo:
... requer alguns segundos de reflexão extra para simular versus outros padrões. Não é uma regra rígida e rápida, mas saltar a instrução break com a continuação parece confuso ou inteligente sem ser útil, e colocar a instrução break no final do ciclo, mesmo que funcione, é incomum. Normalmente ambos
break
econtinue
são usados para evacuar prematuramente o loop ou a iteração do loop, portanto, ver qualquer um deles no final parece um pouco estranho, mesmo que não seja.Fora isso, coisas boas!
fonte
s answer: using a
while` loop desta maneira pode levar à duplicação desnecessária de código para o exemplo dado. E esse é um problema que é IMHO pelo menos tão importante quanto a legibilidade geral do loop.while
, acho que a parte "apenas à primeira vista" é injustificada .while
sintaxe declarativa de carregamento antecipado . Eu não gosto de fazer algo antes do loop que também é feito dentro do loop; então, novamente, eu também não gosto de um loop que se liga a nós tentando refatorar o prefixo. Então, um pouco de um ato de equilíbrio de qualquer maneira.Meu instrutor de lógica na escola sempre dizia e metia no meu cérebro que só deveria haver uma entrada e uma saída para os loops. Caso contrário, você começará a obter o código espaguete e não saberá para onde o código está indo durante a depuração.
Na vida real, acho que a legibilidade do código é realmente importante, de modo que, se alguém precisar corrigir ou depurar um problema com seu código, é fácil para eles.
Além disso, se for um problema de desempenho, porque você está executando esse visual milhões de vezes o loop for pode ser mais rápido. O teste responderia a essa pergunta.
Não conheço C ++, mas entendi completamente os dois primeiros exemplos de código. Estou assumindo que continue vai até o final do loop for e depois o executa novamente. O exemplo while eu entendi completamente, mas para mim foi mais fácil entender do que o seu exemplo (;;). O terceiro, eu teria que fazer algum trabalho de reflexão para descobrir isso.
Então, passando por mim o que era mais fácil de ler (sem saber c ++) e o que meu instrutor de lógica disse que eu usaria o loop while.
fonte
para mim, a tradução C mais direta do pseudocódigo é
Não entendo por que essa tradução óbvia é um problema.
talvez isto:
isso parece mais simples.
fonte