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 fgets
para 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
line
que estamos pedindo fgets
para ler. Esse fato - que podemos dizer fgets
quanto é permitido ler - significa que podemos ter certeza de que fgets
nã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
scanf
apelo 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 y
ou
n
como 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 scanf
formato %s
teria 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 line
e onde 512 é o tamanho da matriz line
para fgets
que 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 line
sido 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 sizeof
operador 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, fgets
nã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,
fgets
lê os caracteres e os preenche em sua matriz até encontrar o \n
caractere que termina a linha e preenche o \n
caractere 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
atoi
ou 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 fgets
está começando a parecer um incômodo. Ligar scanf
era 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 fgets
incô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 scanf
corretamente, você também deve verificar o valor de retorno.
Você tem que tirar as \n
costas. 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's
17 diferentes incômodos, eu o levarei a fgets
qualquer 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 strtol
e strtod
.
strtol
também permite usar uma base diferente de 10, o que significa que você pode obter o efeito de (entre outras coisas) %o
ou %x
comscanf
. 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 scanf
bastante 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 strchr
ou strrchr
, ou strspn
ou strcspn
, ou strpbrk
. Ou você pode analisar / converter e pular grupos de caracteres de dígitos usando as funções strtol
ou
strtod
que 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)) != 2
não detecta como incorreto o texto não numérico à direita.