Muitos programas recebem seus dados de algum grande arquivo. Convém organizar tais programas como um processo iterativo em que cada iteração tem três passos:
O passo 2 pode ser executado pela função sscanf da biblioteca stdio. O passo 1 pode ser executado pela função fgets da biblioteca stdio. Este capítulo oferece uma alternativa caseira para fgets que é mais confortável e mais poderosa. (Trata-se de uma variante da função getline.)
Grande parte de nossos dados é armazenada em arquivos de texto (também conhecidos como plain text ou texto simples). Num arquivo de texto, os bytes representam caracteres. Suporemos que todos os nossos arquivos usam codificação UTF-8 e portanto cada caractere é representado por 1, 2, 3, ou 4 bytes consecutivos. (Convém lembrar que certos bytes não fazem parte do código de nenhum caractere; é o caso dos bytes no intervalo 248 . . 255, por exemplo.) Se cada caractere do arquivo é representado por apenas 1 byte — ou seja, se todos os caracteres são ASCII — temos um arquivo ASCII.
Em geral, um arquivo de texto tem várias linhas, sendo o fim de cada linha sinalizado pelo byte \n. Diremos que cada linha do arquivo é uma linha de texto. (A propósito, o último byte de todo arquivo de texto bem educado deve ser um \n; além disso, nenhuma linha deve ter um espaço antes de \n.)
Eric Roberts escreveu uma biblioteca simpio (veja a interface simpio.h e a implementação simpio.c) cujas funções tornam confortável a leitura de uma linha de texto. A principal função da biblioteca é ReadLine, que substitui, com vantagem, a função fgets (mas talvez não seja tão rápida quanto aquela).
Segue uma versão ligeiramente modificada da função ReadLine de Roberts. A função lê uma linha de comprimento arbitrário de um arquivo de texto.
#include <stdlib.h>
#include <string.h>
typedef unsigned char byte;
// A função readLine lê uma linha (a partir da posição
// corrente) do arquivo de texto infile e devolve uma
// string com o mesmo conteúdo da linha. O byte \n que
// sinaliza o fim da linha não é armazenado como parte
// da string. A função devolve NULL se a posição
// corrente do arquivo estiver no fim do arquivo. Uso
// típico da função:
// s = readLine (infile);
// (Esta função é uma adaptação da função ReadLine da
// biblioteca simpio de Eric Roberts.)
byte *readLine (FILE *infile)
{
int n = 0, size = 128, ch;
byte *line;
line = malloc (size + 1);
while ((ch = getc (infile)) != '\n' && ch != EOF) {
if (n == size) {
size *= 2;
line = realloc (line, size + 1);
}
line[n++] = ch;
}
if (n == 0 && ch == EOF) {
free (line);
return NULL;
}
line[n] = '\0';
line = realloc (line, n + 1);
return line;
}
A função transfere bytes do arquivo infile para o vetor line alocado dinamicamente. Toda vez que o vetor fica cheio, seu tamanho é dobrado.
A função readLine supõe, implicitamente, que a linha lida não contém bytes nulos (uma hipótese perfeitamente razoável).
int n = 0, size = 128, ch; byte *line, *nline; line = malloc (size + 1); while ((ch = getc (infile)) != '\n' && ch != EOF) { if (n == size) { size *= 2; nline = malloc (size + 1); strncpy (nline, line, n); free (line); line = nline; } line[n++] = ch; } if (n == 0 && ch == EOF) { free (line); return NULL; } line[n] = '\0'; nline = malloc (n + 1); strcpy (nline, line); free (line); return nline;
A função GetInteger da biblioteca simpio de Eric Roberts usa a função ReadLine para extrair um número inteiro de uma linha de texto digitada no teclado. Segue uma versão ligeiramente modificada da função:
// Lê uma linha de texto do teclado, extrai dela um
// número inteiro e devolve esse inteiro. Se a linha
// tiver algum byte não branco antes ou depois do
// inteiro a função dá ao usuário a oportunidade de
// tentar de novo. Uso típico: i = getInteger ();
// (Esta função é uma adaptação da função GetInteger
// da biblioteca simpio de Eric Roberts.)
int getInteger (void) {
while (1) {
byte *line = readLine (stdin);
int value;
byte ch;
switch (sscanf (line, " %d %c", &value, &ch)) {
case 1:
free (line);
return value;
case 2:
printf ("Byte inesperado: '%c'\n", ch);
default:
printf ("Tente de novo\n");
}
free (line);
}
}
A função lê uma linha de texto e em seguida usa sscanf para extrair dela um inteiro. Ler uma linha completa é essencial para que a função possa se recobrar de um eventual erro de digitação do usuário; caso contrário, os bytes depois da ocorrência do erro permaneceriam no buffer de entrada e confundiriam as operações de entrada subsequentes.
A função sscanf da biblioteca stdio é como a função fscanf, exceto que opera sobre uma string em lugar de um arquivo. A função recebe uma string line e tenta extrair dela os objetos especificados pelo formato dado no segundo argumento. A função devolve o número de objetos que conseguiu extrair com sucesso de line.