mirror of
https://gitlab.com/blau_araujo/cblc.git
synced 2025-05-09 18:16:37 -03:00
conteúdo da aula 12
This commit is contained in:
parent
efe8315b14
commit
8362cdb96f
1 changed files with 287 additions and 0 deletions
287
aulas/12-fgets/README.org
Normal file
287
aulas/12-fgets/README.org
Normal file
|
@ -0,0 +1,287 @@
|
|||
#+title: Curso Básico da Linguagem C
|
||||
#+subtitle: Aula 12: Leitura da entrada padrão com 'fgets'
|
||||
#+author: Blau Araujo
|
||||
#+startup: show2levels
|
||||
#+options: toc:3
|
||||
|
||||
* Aula 12: Leitura da entrada padrão com 'fgets'
|
||||
|
||||
[[https://youtu.be/ZZr9HBPo0Oc][Vídeo desta aula]]
|
||||
|
||||
Se, por um lado, a função =scanf= é conveniente para ler e converter dados na
|
||||
entrada padrão, também é preciso considerar os cuidados que ela exige,
|
||||
especialmente quando lidamos com a passagem dos dados lidos para um buffer
|
||||
de caracteres (geralmente, criado para receber uma string). A sua principal
|
||||
causa de problemas é a falta de um controle simples e explícito da quantidade
|
||||
de bytes que poderão ser escritos na memória. Na função =scanf=, todo controle
|
||||
possível é aquele que escrevemos em sua string de formato, por exemplo:
|
||||
|
||||
#+begin_src c
|
||||
char buf[10];
|
||||
scanf("%9s", buf);
|
||||
#+end_src
|
||||
|
||||
Aqui, 9 primeiros bytes lidos serão consumidos, acrescidos do terminador nulo
|
||||
(totalizando 10 bytes) e copiados para o endereço expresso pelo vetor =buf=, que
|
||||
foi declarado para receber até 10 bytes (incluindo o terminador nulo). Mas,
|
||||
pode acontecer de, um dia, nós mudarmos de ideia e reduzirmos o limite de =buf=,
|
||||
digamos, para 8 bytes. Isso exigiria uma alteração na chamada de =scanf= -- o que
|
||||
pode ser facilmente esquecido!
|
||||
|
||||
Aliás, essa é uma das justificativas para o uso de constantes simbólicas em
|
||||
vez dos chamados /números mágicos/. Então, é comum que vetores, como o nosso vetor
|
||||
=buf=, sejam declarado assim:
|
||||
|
||||
#+begin_src c
|
||||
#include <stdio.h>
|
||||
...
|
||||
#define BUFMAX 10
|
||||
...
|
||||
int main(...) {
|
||||
char buf[BUFMAX];
|
||||
...
|
||||
scanf("%9s", buf);
|
||||
...
|
||||
}
|
||||
#+end_src
|
||||
|
||||
E isso tornaria ainda mais arriscada qualquer modificação no tamanho definido
|
||||
por =BUFMAX=.
|
||||
|
||||
** A função 'fgets'
|
||||
|
||||
Segundo sua /man page/, a função =fgets= recebe três argumentos:
|
||||
|
||||
#+begin_example
|
||||
char *fgets(char s[restrict .size], int size, FILE *restrict stream);
|
||||
#+end_example
|
||||
|
||||
- =char s[restrict .size]= - Um buffer para receber os dados lidos;
|
||||
- =int size= - A quantidade máxima de bytes a serem escritos no buffer;
|
||||
- =FILE *restrict stream= - Uma estrutura do tipo =FILE= representando o fluxo de
|
||||
dados que será lido.
|
||||
|
||||
Quando chamada, a função =fgets= lê, no máximo, a quantidade de bytes expressa
|
||||
por =size - 1=, inclui o terminador nulo (='\0'=) e escreve esses bytes no buffer.
|
||||
|
||||
Por exemplo:
|
||||
|
||||
#+begin_src c
|
||||
#include <stdio.h>
|
||||
|
||||
#define BUFMAX 10
|
||||
|
||||
int main(void) {
|
||||
char buf[BUFMAX];
|
||||
fgets(buf, BUFMAX, stdin);
|
||||
|
||||
...
|
||||
}
|
||||
#+end_src
|
||||
|
||||
#+begin_quote
|
||||
Aqui, =stdin= é uma macro que expande uma estrutura =FILE= predefinida com o
|
||||
descritor de arquivos =0= (entrada padrão).
|
||||
#+end_quote
|
||||
|
||||
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
|
||||
|
||||
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=).
|
||||
|
||||
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=.
|
||||
|
||||
- =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~).
|
||||
|
||||
Se isso for relevante, nós podemos chamar a função =fgets= desta forma:
|
||||
|
||||
#+begin_src c
|
||||
#include <stdio.h>
|
||||
|
||||
#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
|
||||
|
||||
** O problema da quebra de linha
|
||||
|
||||
A função =fgets= lê, no máximo =size - 1= bytes, o que é ótimo, mas nem sempre
|
||||
a linha lida terá tantos bytes. Nesses casos, a leitura será interrompida
|
||||
depois de uma quebra de linha (caractere ='\n'=) ou depois de alcançado o fim
|
||||
do arquivo (=EOF=). Ou seja, se a leitura terminar com uma quebra de linha,
|
||||
o caractere ='\n'= continuará entre os bytes que serão escritos no buffer.
|
||||
|
||||
Observe:
|
||||
|
||||
#+begin_src c
|
||||
#include <stdio.h>
|
||||
|
||||
#define BUFMAX 10
|
||||
|
||||
int main(void) {
|
||||
char buf[BUFMAX];
|
||||
|
||||
printf("Digite até %d caracteres: ", BUFMAX - 1);
|
||||
fgets(buf, BUFMAX, stdin);
|
||||
printf("%s\n", buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#+end_src
|
||||
|
||||
Executando o programa:
|
||||
|
||||
#+begin_example
|
||||
:~$ ./a.out
|
||||
Digite até 9 caracteres: 123456789
|
||||
123456789
|
||||
:~$ ./a.out
|
||||
Digite até 9 caracteres: 12345
|
||||
12345
|
||||
|
||||
:~$
|
||||
#+end_example
|
||||
|
||||
Quando eu executei o programa pela primeira vez, eu digitei exatamente 9
|
||||
caracteres e a quebra de linha (inserida com o =Enter=) não chegou a ser lida
|
||||
por =fgets=.
|
||||
|
||||
#+begin_example
|
||||
+------+------+------+------+------+------+------+------+------+------+
|
||||
buf[10] | 0x31 | 0x32 | 0x33 | 0x34 | 0x35 | 0x36 | 0x37 | 0x38 | 0x39 | 0x00 |
|
||||
+------+------+------+------+------+------+------+------+------+------+
|
||||
#+end_example
|
||||
|
||||
Mas, na segunda vez, eu digitei apenas 5 caracteres e teclei =Enter=, totalizando
|
||||
os 6 caracteres lidos por =fgets=. Sendo assim, a quebra de linha final também foi
|
||||
escrita no buffer e impressa.
|
||||
|
||||
#+begin_example
|
||||
+------+------+------+------+------+------+------+------+------+------+
|
||||
buf[10] | 0x31 | 0x32 | 0x33 | 0x34 | 0x35 | 0x0A | 0x00 | lixo | lixo | lixo |
|
||||
+------+------+------+------+------+------+------+------+------+------+
|
||||
#+end_example
|
||||
|
||||
|
||||
Pode parecer um inconveniente se estivermos lidando com a digitação de linhas no
|
||||
terminal, mas não remover o caractere ='\n'= faz todo sentido quando estamos lendo
|
||||
linhas de arquivos!
|
||||
|
||||
De todo modo, nós podemos criar uma função para remover a quebra de linha, se for
|
||||
o caso:
|
||||
|
||||
#+begin_src c
|
||||
void trim_string(char *str, int size) {
|
||||
int i = 0;
|
||||
while (i < size && str[i] != '\0') {
|
||||
if (str[i] == '\n') {
|
||||
str[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
#+end_src
|
||||
|
||||
#+begin_quote
|
||||
Se estiver entrando com um buffer muito grande, o tipo de =size= pode ser
|
||||
alterado para =size_t= (=unsigned long int=).
|
||||
#+end_quote
|
||||
|
||||
No nosso exemplo, a função =trim_string= poderia ser utilizada assim:
|
||||
|
||||
#+begin_src c
|
||||
# include <stdio.h>
|
||||
|
||||
#define BUFMAX 10;
|
||||
|
||||
// Remove a quebra de linha final do buffer...
|
||||
void trim_string(char *str, int size);
|
||||
|
||||
int main(void) {
|
||||
char buf[BUFMAX];
|
||||
|
||||
printf("Digite até %d caracteres: ", BUFMAX - 1);
|
||||
fgets(buf, BUFMAX, stdin);
|
||||
trim_string(buf, BUFMAX);
|
||||
|
||||
printf("%s\n", buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Remove a quebra de linha final do buffer...
|
||||
void trim_string(char *str, int size) {
|
||||
int i = 0;
|
||||
while (i < size && str[i] != '\0') {
|
||||
if (str[i] == '\n') {
|
||||
str[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
#+end_src
|
||||
|
||||
Compilando e executando novamente:
|
||||
|
||||
#+begin_example
|
||||
:~$ ./a.out
|
||||
Digite até 9 caracteres: 12345
|
||||
12345
|
||||
:~$
|
||||
#+end_example
|
||||
|
||||
E o conteúdo do buffer seria:
|
||||
|
||||
#+begin_example
|
||||
+------+------+------+------+------+------+------+------+------+------+
|
||||
buf[10] | 0x31 | 0x32 | 0x33 | 0x34 | 0x35 | 0x00 | 0x00 | lixo | lixo | lixo |
|
||||
+------+------+------+------+------+------+------+------+------+------+
|
||||
#+end_example
|
||||
|
||||
** Uma nota sobre o tipo ponteiro para FILE
|
||||
|
||||
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 (ex: =0= ou =STDIN_FILENO=, numa leitura da entrada padrão).
|
||||
|
||||
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:
|
||||
|
||||
| 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=. |
|
Loading…
Add table
Reference in a new issue