diff --git a/aulas/12-fgets/README.org b/aulas/12-fgets/README.org index 36fd1e9..45577fe 100644 --- a/aulas/12-fgets/README.org +++ b/aulas/12-fgets/README.org @@ -87,47 +87,31 @@ descritor de arquivos =0= (entrada padrão). De pronto, fica evidente a facilidade que nós termos para alterar o tamanho do vetor =buf= quando necessário: basta alterar a constante simbólica =BUFMAX=. -*** Retorno nulo ambíguo +*** Uma nota sobre o tipo ponteiro para FILE -No caso de sucesso, a função =fgets= retorna o endereço do buffer ou =NULL=, -no caso de um erro, ou se o fim do arquivo for alcançado sem que algo seja lido -(por exemplo, ao ler um arquivo vazio ou numa leitura interativa cancelada com a -combinação de teclas =Ctrl+D=). +No nível do sistema, o acesso a arquivos é sempre estabelecido através de uma +estrutura complexa identificada por um número inteiro: o descritor de arquivos, +assunto da [[../10-dataio/README.org#headline-2][aula 10]]. Então, quando utilizamos chamadas de sistema, como =read= e +=write=, o fluxo de dados (/stream/) é passado como o número de um descritor de +arquivos. Por isso, nós utilizamos as macros: -Como você pode ver, o significado do retorno =NULL= é ambíguo, mas isso só será -uma preocupação se estivermos lendo arquivos. De todo modo, nós podemos testar -o que aconteceu com as funções =feof= e/ou =ferror=. +| Macro | Significado | +|---------------+----------------------------------------------------| +| =STDIN_FILENO= | Valor inteiro relativo ao descritor de arquivos =0=. | +| =STDOUT_FILENO= | Valor inteiro relativo ao descritor de arquivos =1=. | +| =STDERR_FILENO= | Valor inteiro relativo ao descritor de arquivos =2=. | -- =feof(FILE *stream)= - Testa o estado do indicador de fim de arquivo (~0 = não difinido~). -- =ferror(FILE *stream)= - Testa o estado do indicador de erros (~0 = não definido~). +Nas funções da biblioteca padrão, que abstrai outras estruturas de controle +associadas aos descritores de arquivos, os fluxos de dados são passados como +ponteiros para essas estruturas através do tipo =FILE *= (ponteiro para =FILE=). +Os fluxos de dados padrão também são abstraídos como macros definidas na +biblioteca de funções: -Se isso for relevante, nós podemos chamar a função =fgets= desta forma: - -#+begin_src c -#include - -#define BUFMAX 10 - -int main(void) { - char buf[BUFMAX]; - - if (fgets(buf, BUFMAX, stdin) == NULL) { - // Só preciamos testar se o retorno for NULL... - if (feof(stdin)) { - // Se feof() retornar algo diferente de zero... - fprintf(stderr, "Fim de arquivo alcançado.\n"); - } else if (ferror(stdin)) { - // Se ferror() retornar algo diferente de zero... - fprintf(stderr, "Erro de leitura.\n"); - } - return 1; - } - - ... - - return 0; -} -#+end_src +| Macro | Significado | +|--------+----------------------------------------------------------| +| =stdin= | Ponteiro para =FILE= associado ao descritor de arquivos =0=. | +| =stdout= | Ponteiro para =FILE= associado ao descritor de arquivos =1=. | +| =stderr= | Ponteiro para =FILE= associado ao descritor de arquivos =2=. | ** O problema da quebra de linha @@ -266,28 +250,198 @@ buf[10] | 0x31 | 0x32 | 0x33 | 0x34 | 0x35 | 0x00 | 0x00 | lixo | lixo | lixo | +------+------+------+------+------+------+------+------+------+------+ #+end_example -** Uma nota sobre o tipo ponteiro para FILE +** Retorno nulo ambíguo -No nível do sistema, o acesso a arquivos é sempre estabelecido através de uma -estrutura complexa identificada por um número inteiro: o descritor de arquivos, -assunto da [[../10-dataio/README.org#headline-2][aula 10]]. Então, quando utilizamos chamadas de sistema, como =read= e -=write=, o fluxo de dados (/stream/) é passado como o número de um descritor de -arquivos. Por isso, nós utilizamos as macros: +No caso de sucesso, a função =fgets= retorna o endereço do buffer ou =NULL=, +no caso de um erro, ou se o fim do arquivo for alcançado sem que algo seja lido +(por exemplo, ao ler um arquivo vazio ou numa leitura interativa cancelada com a +combinação de teclas =Ctrl+D=). -| Macro | Significado | -|---------------+----------------------------------------------------| -| =STDIN_FILENO= | Valor inteiro relativo ao descritor de arquivos =0=. | -| =STDOUT_FILENO= | Valor inteiro relativo ao descritor de arquivos =1=. | -| =STDERR_FILENO= | Valor inteiro relativo ao descritor de arquivos =2=. | +Como você pode ver, o significado do retorno =NULL= é ambíguo, mas isso só será +uma preocupação se estivermos lendo arquivos. De todo modo, nós podemos testar +o que aconteceu com as funções =feof= e/ou =ferror=. -Nas funções da biblioteca padrão, que abstraem outras estruturas de controle -associadas aos descritores de arquivos, os fluxos de dados são passados como -ponteiros para essas estruturas através do tipo =FILE *= (ponteiro para =FILE=). -Os fluxos de dados padrão também são abstraídos como macros definidas na -biblioteca de funções: +- =feof(FILE *stream)= - Testa o estado do indicador de fim de arquivo (~0 = não difinido~). +- =ferror(FILE *stream)= - Testa o estado do indicador de erros (~0 = não definido~). -| Macro | Significado | -|--------+----------------------------------------------------------| -| =stdin= | Ponteiro para =FILE= associado ao descritor de arquivos =0=. | -| =stdout= | Ponteiro para =FILE= associado ao descritor de arquivos =1=. | -| =stderr= | Ponteiro para =FILE= associado ao descritor de arquivos =2=. | +Se isso for relevante, nós podemos chamar a função =fgets= desta forma: + +#+begin_src c +#include + +#define BUFMAX 10 + +int main(void) { + char buf[BUFMAX]; + + if (fgets(buf, BUFMAX, stdin) == NULL) { + // Só preciamos testar se o retorno for NULL... + if (feof(stdin)) { + // Se feof() retornar algo diferente de zero... + fprintf(stderr, "Fim de arquivo alcançado.\n"); + } else if (ferror(stdin)) { + // Se ferror() retornar algo diferente de zero... + fprintf(stderr, "Erro de leitura.\n"); + } + return 1; + } + + ... + + return 0; +} +#+end_src + +** Conversão para números + +Diferente da função =scanf=, =fgets= apenas lê caracteres e escreve num buffer +como uma string (caracteres terminados com ='\0'=). Isso significa que, se +estivermos interessados em valores numéricos, os bytes no buffer terão que +ser tratado, geralmente em três etapas: + +- Remoção da quebra de linha, se houver uma; +- Validação dos caracteres segundo o tipo esperado (=int=, =float=, =double=, etc); +- Conversão. + +Nós já falamos sobre a remoção da quebra de linha, então podemos nos concentrar +nas duas etapas seguintes. Porém, os métodos de conversão mais comuns já integram +alguma forma de validação (geralmente, como retornos). Sendo assim, vamos nos +concentrar no que pode ser feito para obter os tipos esperados. + +*** Conversão para inteiros com 'sscanf' + +Provavelmente, a forma mais simples e direta para converter o conteúdo +de um buffer para tipos numéricos é com a função =sscanf=: + +#+begin_src c +char buf[BUFMAX]; +int i; + +fgets(buf, BUFMAX, stdin); + +sscanf(buf, "%d", &i); +#+end_src + +Aqui, se houver dígitos válidos no começo de =buf=, eles serão convertidos +em seu correspondente inteiro na base 10, exatamente como seria feito +lendo uma entrada digitada no terminal com a função =scanf= -- e com suas +mesmas limitações: + +- A dificuldade de detectar entradas inválidas (por exemplo, =42abc= seria + casado e convertido para o inteiro =42=); +- O retorno será apenas a quantidade de casamentos com a string de formato + que forem convertidos e escritos nas variáveis, ou =EOF=, no caso do fim + do buffer ser alcançado antes de um primeiro casamento ou no caso de + um erro; +- Não oferece uma detecção de erros detalhada, como com as funções que + alteram a variável =errno=. + +Além disso, =sscanf= não lida bem com valores fora dos limites do tipo de +destino, por exemplo: + +#+begin_src c +#include +#include + +int main(void) { + char str[] = "9999999999"; + int i = 0; + + sscanf(str, "%d", &i); + + printf("Valor de INT_MAX : %d\n", INT_MAX); + printf("Dígitos na string: %s\n", str); + printf("Valor convertido : %d\n", i); + + return 0; +} +#+end_src + + +Neste exemplo, o comportamento de =sscanf= é indeterminado, mas compilaria +sem erros: + +#+begin_example +:~$ gcc -Wall teste.c +:~$ ./a.out +Valor de INT_MAX : 2147483647 +Dígitos na string: 9999999999 +Valor convertido : 1410065407 +#+end_example + +Aqui, =9999999999= é convertido para um valor com 5 bytes (=0x02540be3ff=), +dos quais, apenas os 4 últimos cabem no espaço do tipo =int= (=0x540be3ff=), +resultando no valor =1410065407=. + +*** Conversão para inteiros longos com 'strtol' + +A função =strtol= converte a parte inicial de uma string para =long= de +acordo com a base de numeração indicada. O endereço do primeiro caractere +inválido é atribuído a um ponteiro -- e isso nos dá uma excelente forma +de controle da conversão. + +Simplificadamente, este seria o protótipo da função: + +#+begin_src c +long strtol(char *str, char **endptr, int base); +#+end_src + +Exemplo: + +#+begin_src c +#include +#include + +int main(void) { + char str[] = "1234567abc"; + char *end; + + long i = strtol(str, &end, 10); + + printf("Valor convertido : %ld\n", i); + printf("Primeiro caractere inválido: 0x%02x\n", *end); + + return 0; +} +#+end_src + +Compilando e executando: + +#+begin_example +:~$ gcc -Wall teste.c +:~$ ./a.out +Valor convertido: 1234567 +Primeiro caractere inválido: 0x61 +#+end_example + +#+begin_quote +O caractere =0x61= é =a=, na tabela ASCII. +#+end_quote + +Outros detalhes: + +- No caso de valores maiores que o limite máximo de um inteiro longo, + o retorno será =LONG_MAX=. +- No caso de valores menores que o limite mínimo de um inteiro longo, + o retorno será =LONG_MIN=. +- Em ambos os casos, a variável =errno= recebe o valor de =ERANGE= (valor + fora da faixa do tipo). +- Se o valor for inválido para a base de numeração, =errno= recebe + o valor de =EINVAL= (valor inválido para a base). +- A descrição do erro em =errno= pode ser impressa com a função =perror=, + chamada logo após =strtol=. +- Se não houver dígitos e caracteres válidos no começo da string, + o endereço do primeiro caractere inválido será o mesmo da string + e a função retornará =0=. +- O ponto negativo de =strtol= é que o retorno é =long= e, se precisarmos + de um valor =int=, teremos que verificar os limites máximo e mínimo + do valor convertido. + +*** Outras funções de conversão + +- =strtoll= - Idêntica a =strtol=, mas converte para =long long int=. +- =strtod=, =strtof= e =strtold= - Funcionam como =strtol=, mas convertem + para =double=, =float= e =long double=, respectivamente, sem a passagem + de uma base de numeração. +- =atoi=, =atof= e =atol= - São muito simples, mas devem ser evitadas pela + falta de validação e de meios para tratamento de erros.