segunda-feira, 25 de fevereiro de 2008

O scanf engana

Quando falei sobre a função scanf, chamei a atenção para a limpeza do buffer do teclado.
Naquele momento enfatizei que limpar o buffer do teclado evitaria algumas dores de cabeça.
Execute o código abaixo e verifique o seu funcionamento.


001:  #include <stdio.h>
002:  #include <stdlib.h>
003:
004:  int main(int argc, char *argv[])
005:  {
006:    char chr1, chr2;
007:    
008:    printf("Digite o primeiro caractere: ");
009:    scanf("%c", &chr1
010:    printf("Digite o segundo caractere: ");
011:    scanf("%c", &chr2);
012:    printf("Primeiro caractere %c\nSegundo caractere: %c\n", chr1, chr2);
013:    system("PAUSE");
014:    return 0;
015:  }


Não fique nervoso tentando digitar o segundo caractere. Esse problema é bastante comum nos códigos de programadores pouco experientes. Tente limpar o buffer do teclado, utilizando a instrução fflush(stdin) antes de cada scanf. Você verá que agora o segundo caractere é lido.
Por que isso ocorre? Imagine o buffer do teclado como um arquivo seqüencial, onde o scanf retira os dados.
Para o exemplo anterior, na linha 9, o scanf espera que você digite um byte (estamos lendo variáveis do tipo char). O que você faz? Digita o caractere e em seguida o enter. Supondo que você digitou o caractere V, o buffer do teclado fica:


V\n


O \n representa o enter.
Após a execução da linha 9, o caractere V é armazenado na variável chr1, deixando o \n no buffer do teclado. Adivinhe que receberá o \n? Correto, a variável chr2. Por esse motivo ela não é lida!
Quando você usa a instrução fflush(stdin), e limpa o buffer do teclado, o \n sai do buffer, deixando ele limpo para a próxima leitura.

10 comentários:

Daniel disse...

Guiera,

Após feita uma vez o fflush,ele precisa ser feito a cada leitura? ou quando faço no inicio do programa ele já deixa limpo para todas as entradas?

Att,

Daniel

Anderson Guiera disse...

Daniel,

Ele deve ser feito a cada leitura.
Você pode testar o programa exemplo deste post e colocar apenas um fflush(stdin), no início do programa, e verá que o problema persiste.
Para esse teste eu usei o Dev C++.

Sds

Guiera

Anônimo disse...

Aparentemente este comando só funciona no windows.

Unknown disse...

Isso é má pratica de programação, não se esqueçam.O fflush(), foi originalmente desenvolvido para limpar buffer de saída e nao de entrada.O ideal seria usar os recursos que a função scanf nos fornece ou até mesmo utilizar um getchar () (que tambem é má pratica de programaçao) ou uma outra funçao para leitura de dados como a fgets().
Como ja foi dito anteriormente o uso do fgets () em outras plataformas, como a GNU-linux, não funciona.Logo pensar em desenvolver softwares portaveis utlizando a fgets () dessa maneira pode resultar em futuras dores de cabeça.

Unknown disse...

Cara, bom assunto, porém, o fflush não funciona em todos os casos.

Creio que em C++ o cin.ignore() funcione e em C voce pode fazer o seguinte:

char ch;

1_ scanf(" %c", &ch);
ou
2_ scanf("\n%c", &ch);

O exemplo 1 não tenho certeza se funciona, mas o 2 é certeza.
É simples, e garante a "limpeza".

Bom, acho que isso serve.

Lucas disse...

O melhor mesmo é colocar o seguinte depois da leitura do primeiro caractere:

scanf("%*c");

OU

getchar();

Ambos vão tirar o \n do buffer sem precisar gravar em nada.

Como o eudson disse, fflush() não foi criada pra fazer isso, inclusive, na documentação dela, se não me engano eles falam que não fazem idéia do que vai acontecer se você usar fflush(stdin).

E também, se for usar o mesmo código em Linux, vai ter que trocar todos os fflushes por uma função lá, que eu esqueci qual é. Melhor usar uma das funções que eu falei e poupar-se de dor de cabeça.

PS: scanf("%*c") também pode ser usado no lugar de system("PAUSE").

Luiz Guilherme disse...

Anderson Guiera,

Eu preciso fazer um projeto em c para entregar domingo agora na faculdade e estou com algumas duvidas!

A linguagem do projeto e c!

Haveria a possibilidade de você me ajudar??

Entre em contato pelo meu email: luizguilherme@tereon.com.br

Anônimo disse...

setbuf(stdin, NULL);

Anônimo disse...

Eu faço assim... e funciona! ;)

/*------------------------------------------------------------------------------
* Função: le_string
*----------------------------------------------------------------------------*/
int le_string ( char s[ ] , int max )
{
int i = 0;
char letra;

if ( strlen(s) <= 2)
{
for ( i = 0; i < ( max - 1 ); i++ )
{
letra = fgetc(stdin);

if ( ( letra == '\n' ) && ( i == 0 ) ) { i = i - 1; continue; }

if ( letra == '\n' ) break;

s[i] = letra;
}

/* Finaliza a string */
s[i] = 0;
return ( i );
}

else { return 0; }
}

/*------------------------------------------------------------------------------
* Função: limpabuffer
*----------------------------------------------------------------------------*/
void limpabuffer()
{
char c;
while ((c = getchar()) != '\n' && c != EOF);
}

/*------------------------------------------------------------------------------
* Chamadas de funções
*----------------------------------------------------------------------------*/
char op[2];

le_string(op, 2);

limpabuffer();

Anônimo disse...

Tua dica não funciona seu bunda mole