sexta-feira, 15 de janeiro de 2010

Pequenas correções na rota

Como falei no último post da série sobre matrizes, hoje falaremos sobre algumas correções necessárias em nossos códigos.

Trabalhando com uma entrada limpa.
No post citado acima inclui um comando novo, e não expliquei o que ele fazia. Antes de ver o que ele faz, vamos ver um código simples que mostra porque ele é necessário.


#include <stdio.h>
#include <stdlib.h>
 
int main (){
 char ch;
 int i;
 
 for(i = 0; i < 10; i++){
    printf("Digite o %do caracter: ", i+1);
    scanf("%c", &ch);
 }
 getchar();
 return EXIT_SUCCESS;
}


Execute esse código e veja o que acontece.
Você conseguiu digitar os 10 valores?
Não??? Por que será?

O buffer do teclado
Quando você pressiona uma tecla (ou conjunto de teclas) em seu teclado é gerada uma informação: a tecla (ou conjunto de teclas) que foi digitada. Esta informação é armazenada em uma área da memória chamada de buffer do teclado, onde fica disponível para ser utilizada. Este buffer pode armazenar uma quantidade razoável de teclas digitadas que serão usadas assim que forem requisitadas.
No código acima, a função scanf 'recupera' o conteúdo de uma dessas teclas, e o armazena na variável ch.
Mas pararmos para pensar, veremos que para armazenar a letra a, por exemplo, na variável ch usando a função scanf, digitamos duas teclas: a tecla a e a tecla enter.
Quando a scanf 'recupera' a tecla a, a tecla enter continua no buffer. Por isso que na próxima chamada da scanf ela irá recuperar o valor da tecla enter, pulando uma entrada. Este problema é clássico e recebeu um nome: sujeira no buffer de entrada.
Existem algumas formas de resolver este problemas.
Vejamos quais são elas, da mais simples à mais drástica (rsrs).

Lendo 2 e ignorando 1.
Este subtítulo diz tudo. Observe o comando abaixo:

 scanf("%c*c", &ch);

Ele faz exatamente isto: Lê 2 caracteres, e ignora o segundo. Susbstituia a chamada da função scanf no código acima e veja a diferença.

Uma outra forma de obter este mesmo resultado é com o seguinte comando:

 scanf(" %c",&ch);

Observe o espaço entre a " e o %. Pode testar. Tem o mesmo efeito.
E se o usuário cometer o erro de digitar mais de 2 caracter antes do enter? Faça o teste.

Pequenas falhas na entrada
A técnica usada acima é pouco eficiente, como podemos ver com este último teste.
Para resolver pequenas erros como este, podemos limpar o buffer usando a função setbuf.
A função setbuf preenche um buffer informado com um valor também informado. Vamos usar o exemplo do código do post sobre matrizes:

 setbuf(stdin, NULL);

O comando acima preenche o buffer da entrada padrão (stdin) com o valor NULL.
A entrada padrão, no nosso caso, é o buffer do teclado. E a palavra NULL é uma constante padrão do C, e significa um valor nulo (um byte com todos os bits 0). Um buffer preenchido com NULL é considerado limpo/vazio.
Para pequenas falhas como uma ou duas teclas a mais esta técnica funciona bem.
É só colocar este comando na linha seguinte a função scanf original que lê apenas um caracter, desta forma:

 scanf("%c", &ch);
 setbuf(stdin, NULL);

Grandes falhas na entrada
O buffer do teclado possui uma característica interessante. Às vezes ele armazena mais do que realmente 'mostra'. Experimente usar este novo código e digitar uns 15 caracteres ou mais, por exemplo, em cada entrada.
A função setbuf limpa somente o que ela 'vê' armazenado no buffer, e logo depois desta limpeza, o buffer é novamente preenchido com o que estava 'oculto' e, desta forma, continua 'sujo'.
Para resolver isto só com 'força bruta' (é só modo de dizer, diga não à violência ;).
Dentre as muitas formas de limpar a entrada usando 'força bruta', eu, particularmente, gosto desta:

 while(getchar()!= '\n');

O comando acima lê os caracteres deixados no buffer, um a um, até encontrar um enter, e despreza todos eles, inclusive o enter.
Substitua o comando setbuf por este mostrado acima e faça o teste. Pode usar 'força bruta' na hora de testar rsrs.


Dando satisfações ao sistema operacional
Existe um outro detalhe no código que usei como exemplo neste post. É o retorno da função main.
Até o momento nós temos usado o retorno das funções para retornar valores para variáveis ou indicar se a função foi bem sucedida. Para isto tenho usado 0 para indicar falha e 1 para indicar sucesso.
A função main também possui um retorno. Em alguns códigos antigos você vai encontrar a main declarada sem retorno ou com um retorno do tipo void. Mas a um bom tempo ela é declarada por padrão com um retorno do tipo inteiro.
Este retorno indica para o sistema operacional se o programa foi concluído com ou sem falhas, ou seja, 0 indica que não houveram falhas, e qualquer número diferente de 0 indica alguma falha.
Você pode usar as constantes padrões EXIT_SUCCESS, que significa que não houve falhas, ou EXIT_FAILURE, que indica alguma falha, ou usar os números diretamente. É muito comum se deparar com um return 0.

System ou não system?
Muitas pessoas criticam o uso da função system (system("pause"); system("cls");).
Um problema que vejo no uso da função system é que ela deixa o código dependente do sistema operacional.
Um system("cls") funciona muito bem para 'limpar a tela' num ambiente Windows, mas não vai funcionar no Linux, onde seria necessário usar um system("clear"). Existem formas de evitar isto usando bibliotecas externas, que funcionam nos dois ambientes. Como o nosso objetivo aqui é aprender o C padrão, vou continuar usando esta função para 'limpar a tela'. Se você trabalha num ambiente Linux, faça a correção quando necessário.
Quanto ao system("pause");, é muito mais simples de resolver. Basta usar um getchar();. Esta função provocará uma pausa na execução do código até que um enter seja digitado, e funciona nos dois ambientes. Não será exibido "Digite qualquer tecla para continuar...", mas o resultado da execução do programa poderá ser visto normalmente. Procurarei usá-la daqui pra frente.

Até o próximo post.

4 comentários:

  1. excelent post! para completar.. o que acontece fik muito nitido no código que segue:
    [code]
    #include
    int main ()
    {
    int i;
    char ch;

    for ( i = 0; i < 10; i++)
    {
    printf("digite um character para a posicao %d:\n", i);
    scanf("%c", &ch);
    printf ("Voce pressionou a tecla %c\n",ch);
    }
    return(0);
    }
    [/code]

    o problema pode ser tbm solucionado utilizando o [i]getch[/i] da biblioteca [i]conio.h[/i] como mostra o exemplo a seguir:

    [code]
    #include
    #include
    int main ()
    {
    int i;
    char ch;

    for ( i = 0; i < 10; i++)
    {
    printf("digite um character para a posicao %d:\n", i);
    //scanf("%c", &ch);
    ch = getch();
    printf ("Voce pressionou a tecla %c\n",ch);
    }
    return(0);
    }
    [/code]

    A vantagem desta função é o fato dela n precisar de um , ela coleta o character q vc digitar. A desvantagem fatal é a portabilidade, a biblioteca [i]conio.h[/i] a principio esta disponivel apenas para ambiente Windows.

    espero ter ajudado

    ResponderExcluir
  2. apenas uma correção.. n existe a linha "//scanf("%c", &ch);".

    eu deixei ela como comentario pra vcs saberem ond ela entraria, no entanto o padrão Ansi89 n admite esse tipo de comentario. ele é adotado apenas no Ansi99.

    ResponderExcluir
  3. Discordo fortemente com o amigo acima, pois a biblioteca conio.h não é uma biblioteca portavel, fazendo assim com que o codigo do exemplo continue sendo o mais indicado.
    att,

    Jonatan Kapps
    Desenvolvedor .NET

    ResponderExcluir
  4. Muito boa a dica sobre a utilização de força bruta.

    ResponderExcluir