Nesta resposta, vou assumir que você está lendo e interpretando linhas de texto . Talvez você esteja solicitando ao usuário, que está digitando alguma coisa e pressionando RETURN. Ou talvez você esteja lendo linhas de texto estruturado de algum tipo de arquivo de dados.
Como você está lendo linhas de texto, faz sentido organizar seu código em torno de uma função de biblioteca que lê, bem, uma linha de texto. A função Padrão é fgets(), embora existam outras (inclusive getline). E então o próximo passo é interpretar essa linha de texto de alguma forma.
Aqui está a receita básica para ligar fgetspara ler uma linha de texto:
char line[512];
printf("type something:\n");
fgets(line, 512, stdin);
printf("you typed: %s", line);
Isso simplesmente lê uma linha de texto e a imprime novamente. Como está escrito, ele tem algumas limitações, as quais abordaremos em um minuto. Ele também possui um recurso muito bom: esse número 512 que passamos como segundo argumento fgetsé o tamanho da matriz
lineque estamos pedindo fgetspara ler. Esse fato - que podemos dizer fgetsquanto é permitido ler - significa que podemos ter certeza de que fgetsnão excederá o array lendo demais nele.
Agora, agora, sabemos ler uma linha de texto, mas e se realmente quisermos ler um número inteiro, um número de ponto flutuante, um único caractere ou uma única palavra? (Isto é, que se o
scanfapelo que estamos tentando melhorar estava usando um especificador de formato como %d, %f, %c, ou %s?)
É fácil reinterpretar uma linha de texto - uma string - como qualquer uma dessas coisas. Para converter uma string em um número inteiro, a maneira mais simples (embora imperfeita) de fazer isso é chamar atoi(). Para converter para um número de ponto flutuante, existe atof(). (E também existem maneiras melhores, como veremos em um minuto.) Aqui está um exemplo muito simples:
printf("type an integer:\n");
fgets(line, 512, stdin);
int i = atoi(line);
printf("type a floating-point number:\n");
fgets(line, 512, stdin);
float f = atof(line);
printf("you typed %d and %f\n", i, f);
Se você quiser que o usuário digite um único caractere (talvez you
ncomo resposta sim / não), você pode literalmente apenas pegar o primeiro caractere da linha, assim:
printf("type a character:\n");
fgets(line, 512, stdin);
char c = line[0];
printf("you typed %c\n", c);
(Isso ignora, é claro, a possibilidade de o usuário digitar uma resposta com vários caracteres; ignora silenciosamente quaisquer caracteres extras que foram digitados.)
Por fim, se você deseja que o usuário digite uma string definitivamente não contendo espaço em branco, se você deseja tratar a linha de entrada
hello world!
como a sequência "hello"seguida por outra coisa (que é o que o scanfformato %steria feito), nesse caso, eu me enganei um pouco, não é tão fácil reinterpretar a linha dessa maneira, afinal, então a resposta para isso parte da pergunta terá que esperar um pouco.
Mas primeiro quero voltar para três coisas que pulei.
(1) Temos chamado
fgets(line, 512, stdin);
para ler na matriz linee onde 512 é o tamanho da matriz linepara fgetsque não a transborde. Mas, para garantir que 512 seja o número certo (especialmente, para verificar se alguém alterou o programa para alterar o tamanho), você deve ler novamente onde quer que tenha linesido declarado. Isso é um incômodo, então existem duas maneiras muito melhores de manter os tamanhos sincronizados. Você poderia, (a) usar o pré-processador para criar um nome para o tamanho:
#define MAXLINE 512
char line[MAXLINE];
fgets(line, MAXLINE, stdin);
Ou, (b) use o sizeofoperador de C :
fgets(line, sizeof(line), stdin);
(2) O segundo problema é que não temos verificado erros. Ao ler a entrada, você deve sempre verificar a possibilidade de erro. Se, por qualquer motivo, fgetsnão puder ler a linha de texto solicitada, isso indica o retorno de um ponteiro nulo. Então deveríamos estar fazendo coisas como
printf("type something:\n");
if(fgets(line, 512, stdin) == NULL) {
printf("Well, never mind, then.\n");
exit(1);
}
Finalmente, há o problema de que, para ler uma linha de texto,
fgetslê os caracteres e os preenche em sua matriz até encontrar o \ncaractere que termina a linha e preenche o \ncaractere também em sua matriz . Você pode ver isso se modificar um pouco o exemplo anterior:
printf("you typed: \"%s\"\n", line);
Se eu executar isso e digitar "Steve" quando solicitado, ele será impresso
you typed: "Steve
"
Isso "na segunda linha é porque a string que ele leu e imprimiu foi realmente "Steve\n".
Às vezes, essa nova linha extra não importa (como quando ligamos
atoiou atof, pois ambos ignoram qualquer entrada não numérica extra após o número), mas às vezes isso importa muito. Muitas vezes, vamos querer retirar essa nova linha. Existem várias maneiras de fazer isso, que abordarei em um minuto. (Eu sei que tenho falado muito disso. Mas voltarei a todas essas coisas, prometo.)
Nesse ponto, você deve estar pensando: "Pensei que você dissesse que scanf
não era bom, e que esse outro caminho seria muito melhor. Mas fgetsestá começando a parecer um incômodo. Ligar scanfera tão fácil ! Não posso continuar usando?" "
Claro, você pode continuar usando scanf, se quiser. (E, para
coisas realmente simples, de certa forma, é mais simples.) Mas, por favor, não venha chorar quando você falhar por causa de uma de suas 17 peculiaridades e fraquezas, ou entrar em um loop infinito por causa da entrada de seu não esperava, ou quando você não consegue descobrir como usá-lo para fazer algo mais complicado. E vamos dar uma olhada nos fgetsincômodos reais:
Você sempre precisa especificar o tamanho da matriz. Bem, é claro, isso não é um incômodo - é um recurso, porque o estouro de buffer é uma coisa realmente ruim.
Você precisa verificar o valor de retorno. Na verdade, isso é uma lavagem, porque para usar scanfcorretamente, você também deve verificar o valor de retorno.
Você tem que tirar as \ncostas. Isso é, admito, um verdadeiro incômodo. Eu gostaria que houvesse uma função padrão que eu pudesse apontar para você que não tivesse esse pequeno problema. (Por favor, ninguém trate de falar gets.) Mas comparado a scanf's17 diferentes incômodos, eu o levarei a fgetsqualquer dia.
Então, como é que você tira essa nova linha? Três caminhos:
a) Maneira óbvia:
char *p = strchr(line, '\n');
if(p != NULL) *p = '\0';
(b) Maneira complicada e compacta:
strtok(line, "\n");
Infelizmente, este nem sempre funciona.
(c) Outra maneira compacta e levemente obscura:
line[strcspn(line, "\n")] = '\0';
E agora que isso está fora do caminho, podemos voltar para outra coisa que eu pulei: as imperfeições de atoi()e atof(). O problema é que eles não fornecem nenhuma indicação útil de sucesso de sucesso ou fracasso: ignoram silenciosamente as entradas não numéricas à direita e retornam 0 silenciosamente, se não houver nenhuma entrada numérica. As alternativas preferidas - que também têm outras vantagens - são strtole strtod.
strtoltambém permite usar uma base diferente de 10, o que significa que você pode obter o efeito de (entre outras coisas) %oou %xcomscanf. Mas mostrar como usar essas funções corretamente é uma história em si, e seria uma distração demais para o que já está se transformando em uma narrativa bastante fragmentada, então não vou dizer mais nada sobre elas agora.
O restante da narrativa principal diz respeito à entrada que você pode estar tentando analisar que é mais complicado do que apenas um único número ou caractere. E se você quiser ler uma linha que contém dois números, ou várias palavras separadas por espaços em branco, ou pontuação de estrutura específica? É aí que as coisas ficam interessantes e onde as coisas provavelmente estavam ficando complicadas se você estivesse tentando fazer as coisas usando scanf, e onde há muito mais opções agora que você leu uma linha de texto de maneira limpa fgets, embora a história completa de todas essas opções provavelmente poderia encher um livro, então só poderemos arranhar a superfície aqui.
Minha técnica favorita é dividir a linha em "palavras" separadas por espaços em branco e fazer algo mais a cada "palavra". Uma função padrão principal para fazer isso é
strtok(que também tem seus problemas e que também classifica toda uma discussão separada). Minha preferência é uma função dedicada à construção de uma matriz de ponteiros para cada "palavra" desmembrada, uma função que descrevo
nestas notas do curso . De qualquer forma, uma vez que você tenha "palavras", poderá processar cada uma delas, talvez com as mesmas atoi/ atof/ strtol/ strtod
funções que já examinamos.
Paradoxalmente, mesmo que tenhamos gasto scanfbastante tempo e esforço aqui para descobrir como nos afastar , outra boa maneira de lidar com a linha de texto com a qual acabamos de ler
fgetsé passar para ela sscanf. Dessa forma, você acaba com a maioria das vantagens de scanf, mas sem a maioria das desvantagens.
Se sua sintaxe de entrada for particularmente complicada, pode ser apropriado usar uma biblioteca "regexp" para analisá-la.
Finalmente, você pode usar as soluções de análise ad hoc que mais lhe convierem. Você pode mover através da linha um caractere de cada vez com um
char *ponteiro verificando os caracteres esperados. Ou você pode procurar caracteres específicos usando funções como strchrou strrchr, ou strspnou strcspn, ou strpbrk. Ou você pode analisar / converter e pular grupos de caracteres de dígitos usando as funções strtolou
strtodque ignoramos anteriormente.
Obviamente, há muito mais a ser dito, mas espero que esta introdução o inicie.
(r = sscanf("1 2 junk", "%d%d", &x, &y)) != 2não detecta como incorreto o texto não numérico à direita.