Ir ao conteúdo

Posts recomendados

Postado

 

- INTRODUÇÃO DA FUNÇÃO nscanf (N.o.o.b's scanf):

 

Iniciantes na linguagem C tipicamente não entendem como a função scanf funciona e acabam cometendo erros que levam ao mal funcionamento do programa.

 

Dois erros comuns são:

 

- Não considerar que no fim da leitura do scanf normalmente sobra o caractere nova linha '\n' na entrada padrão stdin, e que isso deve ser tratado, ou seja o caractere deve ser lido para que não fique sobrando na stdin correndo o risco de afetar as próximas leituras.

 

- Não considerar que o usuário pode não se comportar, ou seja não digitar aquilo que o programa solicitou e espera que ele digite. Por exemplo, pode digitar um texto quando se espera que digite um número, ou digitar um número com ponto flutuante (float ou double) quando se espera um número inteiro, ou não digitar nada, ou até mesmo digitar o que é esperado mas também continuar digitando coisas além disso na mesma linha, entre outros casos.

 

Então, tendo em vista isto eu fiz esta função nscanf (N.o.o.b's scanf) que se comporta como os iniciantes em C esperam que a função scanf funcione:

 

#include <stdio.h>
#include <stdlib.h>

char *__readline(void){
    static char linebuffer[16384];
    if(fgets(linebuffer, sizeof linebuffer, stdin) == NULL) linebuffer[0] = '\0';
    return linebuffer;
}

#define nscanf(...) sscanf(__readline(), __VA_ARGS__)

 

- COMO USAR:

 

A função nscanf pode ser usada exatamente do mesmo modo que a função scanf , mas diferente da função scanf não ficará sobrando caracteres na entrada padrão em nenhuma situação, mesmo que o usuário não se comporte e a leitura falhe, na próxima leitura stdin já estará totalmente limpa permitindo usar nscanf novamente sem preocupações.


 

 

#include <stdio.h>
#include <stdlib.h>

char *__readline(void){
    static char linebuffer[16384];
    if(fgets(linebuffer, sizeof linebuffer, stdin) == NULL) linebuffer[0] = '\0';
    return linebuffer;
}

#define nscanf(...) sscanf(__readline(), __VA_ARGS__)

int main()
{
    int a;
    char b;
    
    printf("Numero: ");
    nscanf("%d", &a);
    
    printf("Letra: ");
    nscanf("%c", &b);
    
    printf("%d\n%c\n", a, b);
    
    return 0;
}

 

 

- COMO FUNCIONA:

 

O funcionamento da função nscanf é simples, primeiro faz a leitura de todo o conteúdo da stdin com a função __readline , que usa fgets com um vetor/array de char "bem grande" para ler todo tudo que for digitado, e então a passa a string obtida como argumento da função sscanf para ser processada de acordo com os especificadores fornecidos na string de especificadores de formatação da função.

 

O vetor usado na função __readline é declarado como static o que faz com que a memória fique reservada durante toda a execução do programa, não apenas durante a execução da função, permitindo retornar o ponteiro que aponta para o vetor que será usado pela função sscanf , e já que a memória é reservada permanentemente ela também pode ser reutilizada toda vez que usar a função nscanf .

 

 

- DESVANTAGEM:

 

A desvantagem óbvia de usar a função nscanf é o fato de deixar de aprender a usar a função scanf corretamente, isso pode ser um problema principalmente para alguém que é um aluno pois provavelmente vai ter que usar a função scanf durante provas e avaliações, então deve aprender como usar scanf. Então se for usar nscanf, eu recomendo que ainda assim aprenda a usar scanf , e o porquê ela pode não se comportar como você espera em certas situações, mas use nscanf para não precisar lidar com estas situações.

 

Mas vendo isto por outro lado nscanf pode ajudar quem estiver estudando a focar em aprender a programar, ao invés de focar em aprender os comportamentos específicos de funções da linguagem C e como lidar com eles.

 

 

- SOBRE AS FALHAS NA FUNÇÃO PARA PROGRAMADORES EXPERIENTES:

 

Os programadores experientes aqui do fórum certamente vão notar que existe um problema na função __readline , pois como o vetor usado como buffer tem um tamanho fixo existe a possibilidade de que em um caso extremo seja lida uma quantidade de caracteres maior do que este tamanho, e isso causaria uma falha no funcionamento da função, e por outro lado essa pode ser uma quantidade de memória excessiva pois na grande maioria dos casos apenas uma pequena parte desse vetor será usado.

 

Sobre este problema digo que esta versão da função __readline é propositalmente simples, pois é algo que poderia ser usado facilmente por um iniciante, bastando copiar um pequeno código, e adicionar um n aos scanfs usados no programa para poder usar, e é algo que um iniciante pode aprender exatamente como funciona sem muitas dificuldades.

 

Se tiverem interesse em usar uma versão totalmente confiável da função nscanf , com uma função __readline que usa malloc para alocar a quantidade necessária de memória, eu também fiz uma versão assim, e posso postar aqui se desejarem.

 

 

Espero ler seus comentários e criticas sobre a função nscanf... Existem problemas que eu não detectei? Situações que a tornariam pior do que a função scanf?

 

  • Curtir 2
  • Obrigado 1
  • Amei 1
  • 7 meses depois...
Postado

@isrnick Isso aqui está certo? Estava dando uma olhada em o que são/como funcionam macros variadic (nunca tinha visto isso antes, acho que é assim que se chama :D), e fiz o seguinte:

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

#define lscanf(FormatLiteral, ...) scanf(""FormatLiteral"%*c", __VA_ARGS__)

int main()
{
    setlocale(LC_ALL, "Portuguese");

    int x;
    char c;

    printf("Digite um número: ");
    lscanf("%d", &x);

    printf("Digite um caractere: ");
    lscanf("%c", &c);

    printf("\nRespostas: %d, %c", x, c);

    // Verificando sujeira
    printf("\nSe funcionou, ele pausa aqui...");
    scanf("%*c");

    return 0;
}

Só sei que __VA_ARGS__ pode ser usado na definição para inserir os argumentos, e é substituído por todos os argumentos que correspondem às reticências, incluindo vírgulas (referência), e FormatLiteral eu tinha visto aqui em um exemplo com fprintf(), ainda não entendi muito bem o que é (não achei muitas referências), apenas interpretei sua funcionalidade baseado no exemplo.

Eu testei aqui e aparentemente funciona, contudo não para todos os casos, mas já é um começo :Dcaso o '\n' não faça parte da leitura, como em muitos casos quando se usa scanf() para ler do teclado (antes que eu leve outra bronca do Arf, deixo dito que scanf() é pra ler dados formatados), ele fica no buffer, e %*c implica em ler e ignorar esse caractere.

Obrigado desde já por qualquer esclarecimento.

  • Curtir 3
Postado
5 horas atrás, Lucca Rodrigues disse:


// Verificando sujeira
printf("\nSe funcionou, ele pausa aqui...");
scanf("%*c");

Ignore isso, esqueci de retirar quando fui postar o código...

 

Em 19/05/2020 às 00:14, isrnick disse:

Espero ler seus comentários e criticas sobre a função nscanf... Existem problemas que eu não detectei? Situações que a tornariam pior do que a função scanf?

Depois de 7 meses, ainda ta valendo o feedback? 🤪

Achei muito bacana o que fez, só tenho umas perguntas...

Aquele lbuffer é o que? Ou era pra ser linebuffer?

Se uso o scanset negado sem incluir o '\n' num possível conjunto de caracteres, ao dar um Enter ele já encerra a leitura (e garante um '\n' na string como último caractere inserido), isso é proposital? Com scanset, se incluo o '\n' no conjunto e dou um Enter, ele encerra a leitura também.

  • Curtir 1
  • mês depois...
Postado
Em 10/01/2021 às 18:56, Lucca Rodrigues disse:

Depois de 7 meses, ainda ta valendo o feedback? 🤪

 

Claro 👍

 

Em 10/01/2021 às 18:56, Lucca Rodrigues disse:

Aquele lbuffer é o que? Ou era pra ser linebuffer?

 

Sim, estava errado, o correto é linebuffer.

Eu estava certo de que eu já tinha editado e corrigido isso quando criei o tópico, talvez tenha perdido a edição quando o fórum foi atualizado?

De qualquer maneira pedi para os moderadores editarem e corrigirem isso (já que o fórumnão permite mais que eu mesmo edite), e agora o código está correto.

 

Em 10/01/2021 às 18:56, Lucca Rodrigues disse:

Se uso o scanset negado sem incluir o '\n' num possível conjunto de caracteres, ao dar um Enter ele já encerra a leitura (e garante um '\n' na string como último caractere inserido), isso é proposital? Com scanset, se incluo o '\n' no conjunto e dou um Enter, ele encerra a leitura também.

 

Não entendi muito bem a pergunta... Pode clarificar?

Com %[^\n] ele tudo que não for o '\n', logo o '\n' é o único caractere que não é lido, logo fica sobrando o '\n' na entrada padrão stdin. Mas com %[\n] ele só vai identificar e ler o caractere nova linha '\n', e não vai ler nada mais.

  • Obrigado 1
Postado

@isrnick Boa noite! Eu li seu código da nscanf() no qual criou a __readline() de forma semelhante à função no código que postou aqui, mas usando realloc() para realocar memória se necessário (aquele que você postou no servidor do discord), e acho que este ficou até mais bacana, mas o "problema" (que não é problema) que eu estava observando em ambos os códigos era o seguinte: testei o scanset negado sem incluir o '\n' no conjunto de caracteres (ex: %[^abc]), e o scanset, incluindo o '\n' no conjunto (ex: %[\nabc]). Para o primeiro caso, contanto que o usuário não digite os caracteres 'a', 'b' e 'c', a leitura persiste, e para o segundo caso, o usuário pode digitar apenas os caracteres '\n', 'a', 'b' e 'c' se quiser que a leitura persista.

 

O teste:

#include <stdio.h>
#include <stdlib.h>

char *__readline(void){
    static char linebuffer[16384];
    if(fgets(linebuffer, sizeof linebuffer, stdin) == NULL) linebuffer[0] = '\0';
    return linebuffer;
}

#define nscanf(...) sscanf(__readline(), __VA_ARGS__)

int main()
{
    char str1[100];
    char str2[100];

    printf("Testando digitar a string <abc\\nz\\n>:\n");
    nscanf("%[\nabc]", str1);

    printf("<String 1: %s>\n", str1);

    printf("\nTestando digitar a string <ghi\\na\\n>\n");
    nscanf("%[^abc]", str2);

    printf("<String 2: %s>\n", str2);

    return 0;
}

 

A saída:

image.png.ea5a664e86dc62be2aa8e0ca2b4bf9b6.png

 

Enquanto com scanf() (apenas trocando nscanf() por scanf() no código, e lendo o que sobrou na stream de entrada depois da primeira leitura para não interferir na segunda):

image.png.31ee87a2bc421287d72af0eba941190d.png

 

Então desde que seja especificado que '\n' deva ser lido, eu posso digitar quantos eu quiser usando scanf(), mas com nscanf(), como aparentemente você usou fgets() para ler os dados e depois passou a string para sscanf(), a função espera por um Enter para encerrar a leitura em qualquer ocasião. No código que mostrou no discord, tinha lá essa condição para a persistência do while.

//...
while(lbuffer[lbuffersize-1] == '\0' && lbuffer[lbuffersize-2] != '\n'){
//...
// O que eu entendi que esse loop faz é realocar memória e
// continuar lendo caso o usuário não tenha digitado Enter ainda
}
//...

 

 

Quando eu postei #4, ainda estava estudando macros... Bem, aquilo pode até ser útil e simples, mas acho que compensa mais simplesmente colocar um %*c depois dos especificadores para leituras em que o '\n' não interessa. Fora isso, Outra possibilidade seria usar um scanf() e logo depois um loop para ler o que quer que tenha restado no fluxo, mas há a possibilidade de não ter sobrado nada.

Uma ideia seria usar algo que "limpe" o buffer (algo que sobra de uma leitura não é lixo, só uso esse termo porque é comum 😁), mas sem parar para ler algo se o buffer estiver vazio. Pensei em usar:

FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE));

Mas limpar o buffer de entrada da console não tem efeito sobre o que já foi lido e armazenado na stdin. Depois de um tempo, achei isso:

_flushall();

Que faz o que fflush() faria normalmente, mas com todos os buffers abertos, e bem...

#define lscanf(...) scanf(__VA_ARGS__); FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE)); _flushall()

Se houver algo que não foi lido ainda, ou algo que foi lido e deixado de lado, acho que a macro acima pode tratar disso, tendo restado algo na stdin ou não.

 

PS: nunca tinha ouvido falar dessa tal _flushall(), apenas dei uma lida na documentação, então não conheço muito a respeito dos contras além do simples fato dela "liberar" todos os fluxos...

 

PS 2: Acho que quem mais condena o uso da scanf() para ler do teclado aqui no fórum é o Arf, e sua proposta é a PeekConsoleInput(), que de forma resumida vê o que tem pra ler, mas sem ler. Parece não ser tão complicado, são apenas algumas linhas, mas chega a ser compreensível a preferência à scanf() por iniciantes como eu quando nos deparamos com o jeito da API do Windows de ser 😁

Crie uma conta ou entre para comentar

Você precisa ser um usuário para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar agora

Sobre o Clube do Hardware

No ar desde 1996, o Clube do Hardware é uma das maiores, mais antigas e mais respeitadas comunidades sobre tecnologia do Brasil. Leia mais

Direitos autorais

Não permitimos a cópia ou reprodução do conteúdo do nosso site, fórum, newsletters e redes sociais, mesmo citando-se a fonte. Leia mais

×
×
  • Criar novo...

LANÇAMENTO!

eletronica2025-popup.jpg


CLIQUE AQUI E BAIXE AGORA MESMO!