#+title: Curso Básico da Linguagem C #+subtitle: Aula 16: Abertura de arquivos para leitura e escrita #+author: Blau Araujo #+startup: show2levels #+options: toc:3 * Aula 16: Abertura de arquivos para leitura e escrita [[https://youtu.be/B42KIZfivsg][Vídeo desta aula]] ** Modos de abertura para leitura e escrita As strings de modos ="r"=, ="w"= e ="a"=, da função =fopen=, quando sucedidas do caractere =+=, indicam a abertura do arquivo também para suas respectivas operações contrarias: | Modo | Leitura | Escrita | Truncar | Offset | Criar | |------+---------+---------+---------+--------+-------| | ="r"= | Sim | Não | Não | Início | Não | | ="r+"= | Sim | Sim | Não | Início | Não | | ="w"= | Não | Sim | Sim | Início | Sim | | ="w+"= | Sim | Sim | Sim | Início | Sim | | ="a"= | Não | Sim | Não | Fim | Sim | | ="a+"= | Sim | Sim | Não | Fim | Sim | De acordo com a tabela acima, nós precisamos observar alguns pontos importantes: - Se a string de modo iniciar com =w=, o arquivo sempre será truncado na sua abertura. - Se a string de modo iniciar com =a=, o ponteiro interno sempre será posicionado no fim do arquivo na sua abertura. - Se a string de modo iniciar com =r=, o ponteiro interno sempre será posicionado no início do arquivo na sua abertura. - Se não existir, o arquivo só será criado se a string de modo iniciar com =w= ou =a=. ** Manipulação do ponteiro interno O ponteiro interno do arquivo representa o número do byte que será lido ou escrito no próximo acesso: ou seja, seu /offset/ (ou /ponto de partida/). Isso é particularmente importante quando abrimos arquivos para ambas as operações com as strings de modo ="r+"= ou ="a+"=, visto que o modo ="w+"= truncará o conteúdo do arquivo. *** A função =fseek= As funções da família =fseek= (=stdio.h=) são utilizadas para posicionar ou obter informações sobre o ponteiro interno do arquivo: | Função | Descrição | Retorno | |---------+-----------------------------------------------------------------------------------------+--------------------------------| | =fseek= | Define a posição do ponteiro interno. | Sucesso: =0= (=int=) | | =ftell= | Obtém a posição corrente do ponteiro interno. | Sucesso: número do byte (=long=) | | =rewind= | Posiciona o ponteiro interno no byte =0=. | =void= | | =fsetpos= | Interface para =fseek= referenciando a definição do /offset/ a partir do início do arquivo. | Sucesso: =0= (=int=) | | =fgetpos= | Interface para =ftell= escrevendo o /offset/ obtido em um dado endereço. | Sucesso: =0= (=int=) | *Notas:* - Com a função =fseek=, o novo /offset/ é definido com uma expressão do tipo =long=. - Com a função =fsetpos=, o novo /offset/ é definido em um ponteiro do tipo =fpos_t=. - Com a função =fgetpos=, o /offset/ corrente é escrito em um ponteiro do tipo =fpos_t=. #+begin_quote A função =fseek= trabalha com /streams/, mas é possível manipular o ponteiro interno a partir de descritores de arquivos com a chamada de sistema =lseek=. #+end_quote *** Pontos de origem de =fseek= A função =fseek= recebe 3 argumentos: - O /stream/ associado ao arquivo; - A expressão do valor do /offset/ (tipo =long=); - Um inteiro determinando o ponto de origem para a definição do novo /offset/. Este é o seu protótipo na =glibc= (=stdio.h=): #+begin_src c int fseek(FILE *stream, long offset, int whence); #+end_src #+begin_quote O termo /whence/ significa /"a partir de onde"/ e representa o ponto de origem do deslocamento do ponteiro interno do arquivo. #+end_quote O terceiro argumento (=whence=) pode receber qualquer valor inteiro, mas a =glibc= oferece três macros para os pontos de origem mais comuns: - =SEEK_SET=: o início do arquivo; - =SEEK_END=: o fim do arquivo; - =SEEK_CUR=: o /offset/ corrente. *** Abertura de arquivos com "r+" Como o /offset/ inicial será o byte =0=, se a primeira operação for uma escrita, os primeiros bytes do arquivo serão sobrescritos. Caso esta não seja a intenção, será preciso deslocar o ponteiro interno para o fim do arquivo: #+begin_src c // Abertura do arquivo para leitura e escrita... char *file = "arquivo.txt"; FILE *stream = fopen(file, "r+"); // Posicionar offset no fim do arquivo... fseek(stream, 0, SEEK_END); // Rotinas de escrita... #+end_src Aqui, nós deslocamos o ponteiro interno em zero bytes a partir do fim do arquivo (origem em =SEEK_END=). *** Abertura de arquivos com "a+" O /offset/ inicial será o fim do arquivo e, se quisermos começar a leitura a partir de seu início, será preciso deslocar o ponteiro interno para lá: #+begin_src c // Abertura do arquivo para leitura e escrita... char *file = "arquivo.txt"; FILE *stream = fopen(file, "r+"); // Posicionar offset no início do arquivo... fseek(stream, 0, SEEK_SET); // Rotinas de leitura... #+end_src Desta vez, nós deslocamos o ponteiro interno em zero bytes a partir do início do arquivo (origem em =SEEK_SET=). ** Procedimentos úteis Para encerrar esta aula (e o nosso curso), aqui estão alguns exemplos de procedimentos gerais úteis envolvendo a manipulação de arquivos. *** Quantidade de linhas #+begin_src c #include #include #include int main(void) { // Abertura do arquivo para leitura... char *file = "meuarquivo.txt"; FILE *stream = fopen(file, "r"); if (!stream) { fprintf(stderr, "Erro ao abrir o arquivo!\n"); return EXIT_FAILURE; } // Contagem de linhas... char buf[BUFSIZ]; // Buffer para ler blocos do arquivo (8192 bytes). int lines = 0; // Total de linhas encontradas. // Lê uma linha até encontrar \n, até BUFSIZ-1 ou até EOF... while (fgets(buf, BUFSIZ, stream) != NULL) { // Só conta uma linha se houver \n no buffer... if (buf[strlen(buf) - 1] == '\n' ) lines++; } // Se a última linha não contiver \n, ela também será contada... if (lines > 0 && buf[strlen(buf) - 1] != '\n') lines++; // Fechamento do arquivo... fclose(stream); // Impressão do resultado... printf("%d linhas", lines); return EXIT_SUCCESS; } #+end_src *** Obter o tamanho do arquivo #+begin_src c #include #include int main(void) { // Abertura do arquivo... char *file = "meuarquivo.txt"; FILE *stream = fopen(file, "rb"); if (!stream) { perror("Erro ao abrir o arquivo"); return EXIT_FAILURE; } // Posiciona o ponteiro de leitura no final do arquivo... if (fseek(stream, 0, SEEK_END) != 0) { perror("Erro ao deslocar o offset para o final"); fclose(stream); return EXIT_FAILURE; } // Obtém o offset corrente (tamanho do arquivo em bytes)... long fsize = ftell(stream); if (fsize == -1L) { perror("Erro ao obter o offset corrente"); fclose(stream); return EXIT_FAILURE; } // Exibe o tamanho do arquivo... printf("Tamanho do arquivo: %ld bytes\n", fsize); // Fechamento do arquivo... fclose(stream); return EXIT_SUCCESS; } #+end_src *** Testar se um arquivo está vazio (0 bytes) #+begin_src c #include #include int main(void) { // Abertura do arquivo... char *file = "meuarquivo.txt"; FILE *stream = fopen(file, "r"); if (!stream) { perror("Erro na abertura do arquivo"); return EXIT_FAILURE; } // is_empty recebe 1 se o arquivo estiver vazio... int is_empty = (fseek(stream, 0, SEEK_END) == 0 && ftell(stream) == 0); // Fechamento do arquivo... fclose(stream); // O programa terminará com sucesso (0) se o arquivo estiver vazio... return !is_empty; } #+end_src