Documentos de Texto

Introdução

  • Até agora aprendemos como mexer com variáveis na memória do computador. No entanto a memória é volátil, isto é, não retém informações após o fim da execução do programa.
  • Para que possamos guardar informações após a execução do programa, e mesmo que o computador seja desligado, utilizamos arquivos.

Arquivos

  • Um arquivo também é uma abstração do sistema operacional. Arquivos são maneiras convenientes de organizar dados da memória secundária, a memória não volátil. O Sistema operacional é o responsável por gerênciar os arquivos.
  • Para o programador, os arquivos são streams, isto é, sequências de bytes que podem ser manipulados (lidos, gravados, copiados, etc)
  • Um arquivo termina com um caractere especial, conhecido como EOF (End Of File). Esse caractere marca o final da sequência de bytes que compõem o arquivo.
  • Todos os arquivos tem um nome assosciado, que é o nome pelo qual o Sistema Operacional identifica o arquivo.

Tipos de Arquivos

Existem diversas extensões de arquivos no computador, mas em geral apenas 2 tipos de arquivos:

  • Arquivos de Texto

    Cada byte do arquivo representa um caractere. Assim, o conjunto de bytes forma um texto. Um programa C, por exemplo, é um arquivo desse tipo, pode ser facilmente visualizado com qualquer editor de texto. Arquivos texto são fáceis de serem lidos por pessoas, mas costumam ocupar mais espaço para armazenar informações.

  • Arquivos Binários

    são gravados os dados como estariam na memória, byte a byte. Por exemplo, uma variável inteira é gravada com 4 bytes com o conteúdo exato que está na memória. Não conseguimos visualizar com um editor de texto, é necessário um programa que reconheça aquela sequência de bytes e dê significado a ela. Difícil de ser lido por pessoas, mas costuma ocupar menos espaço para armazenar informações, uma vez que cada byte não precisa representar um caractere válido. Imagens e áudios são exemplos de arquivos binários.

Nesta aula vamos conhecer melhor os arquivos de texto. Os arquivos binários ficarão para uma aula futura.

Manipulando Arquivos

  • Na linguagem C os arquivos são abstraídos por um ponteiro para arquivo. Todas as operações feitas no arquivo acontecem através deste ponteiro.
  • Este ponteiro é a ligação entre o seu programa e o arquivo sendo manipulado. Ele também guarda a posição atual do arquivo, que é automaticamente atualizada toda vez que uma leitura ou escrita acontece.
  • No mais, é um ponteiro como qualquer outro. Eles são declarados assim:
    FILE* fd; // ponteiro para arquivo
    Neste caso o ponteiro se chama "fd", mas pode ter qualquer nome.

Abrindo Arquivos

Antes de operarmos com os arquivos precisamos saber algumas informações sobre tal arquivo. Por exemplo:

  • Em qual diretório o arquivo se encontra?
  • O arquivo já existe ou vamos criá-lo?
  • O arquivo existe mas queremos refazê-lo?

Sabendo essas informações poderemos abrir o arquivo corretamente. A função que faz isso é fopen() (file open), que recebe 2 parâmetros, nesta ordem, nome do arquivo e modo em que abriremos o arquivo. Os modos disponíveis estão listados na tabela abaixo.

FILE* fd; // file descriptor
char file[] = "file.txt"; // file name
fd = fopen(file, "w"); // abrindo arquivo para escrita
                                    

Modos de Abertura de Arquivos Texto

Code Nome Descrição
"r" Read Abre um arquivo TEXTO para leitura, já deve existir o arquivo.
"w" Write Abre (ou cria) um arquivo TEXTO para escrita. Se o arquivo já existir seu conteúdo anterior é descartado.
"a" Append Abre (ou cria) um arquivo TEXTO para escrita. Ponteiro começa no final do arquivo.
"r+" Read Plus Abre um arquivo TEXTO para leitura ou escrita. O arquivo deve existir e poder ser modificado.
"w+" Write Plus Abre (ou cria) um arquivo TEXTO para leitura e escrita. Se o arquivo já existir seu conteúdo anterior é descartado.
"a+" Append Plus Abre (ou cria) um arquivo TEXTO para leitura e escrita. Ponteiro começa no final do arquivo.

A função fopen() retorna um descriptor do arquivo, que é gerado pelo SO e associa o arquivo desejado ao ponteiro que criamos. Caso o arquivo não possa ser aberto por qualquer motivo o retorno será NULL. Assim, para saber se o arquivo foi aberto com sucesso basta testar se o ponteiro tem valor NULL.

Veja nos materiais extras alguns motivos que podem causar falha na abertura do arquivo.

FILE* fd;
char file[] = "file.txt";
fd = fopen(file, "r+");

// testar se arquivo existe
if(fd == NULL)
    fd = fopen(file, "w"); // se nao existe, cria o arquivo
                                    

Depois de utilizarmos o arquivo é necessário fechá-lo. Isso é importante por 2 motivos:

  • Liberar espaço alocado para as operações com o arquivo.
  • Garantir que as mudanças feitas no arquivo realmente foram salvas.
Veja nos materiais extras explicações mais detalhadas sobre a importância de fechar arquivos corretamente.

Para fechar um arquivo usamos a função fclose(). Após fecharmos um arquivo a associação entre o arquivo e o ponteiro é desfeita, e poderemos associar o ponteiro à outro arquivo.

FILE* fd = fopen("file.txt", "r+");

// utiliza o arquivo no programa....
fclose(fd); // fecha o arquivo
                                    

Movimentação em Arquivos

O ponteiro do arquivo também guarda a posição atual em que o arquivo está sendo manipulado. Lembre-se que um arquivo é uma sequência de bytes, assim, a posição atual significa qual byte da sequência estamos olhando. Toda vez que uma operação de leitura ou escrita acontece esta posição é automaticamente atualizada, mas por vezes queremos ir para uma posição específica no arquivo. Vejamos algumas das funções que nos ajudam a fazer isso.

  • Se quisermos ir ao começo do arquivo podemos usar a função rewind()
    rewind(fd); // retorna para o inicio do arquivo
  • Para saber a posição atual do arquivo usamos ftell()
    int pos;
    pos = ftell(fd);
                                            
  • Para ir para uma posição específica usamos a função fseek(). Ela recebe 3 parâmetros: o ponteiro para o arquivo, o offset em bytes para movimentação, e a origem. A origem pode receber 2 valores:
    • SEEK_SET - Beginning of file
    • SEEK_CUR - Current position of the file pointer
    fseek(fd, 15, SEEK_SET); // go to 15th byte from beggining
                                            

Existem muitas outras funções para manipulação de arquivos. Elas fazem parte da biblioteca stdio.h . Leia a referência da biblioteca para conhecer mais funções (lembre-se que nem todas as funções desta biblioteca são sobre arquivos).

Arquivos Texto

  • Para facilitar o entendimento, utilizaremos um arquivo chamdo notas.txt, com os seguintes dados:

  • Thiago 9.0
    Joao 8.7
    Maria 8.1
    Henrique 7.6

    Operação de Leitura

    • Para conseguir ler os dados de um arquivo, basta seguir os seguintes passos:

      1. Declarar as variáveis para leitura e um ponteiro para o arquivo;

      2. typedef struct {
            char nome[100];
            double nota;
        }Estudante;
        
        int main(){
            FILE *arquivo;
            int i,n = 0;
            Estudante aluno[100];
      3. Abrir o arquivo no modo leitura;

      4. arquivo = fopen("notas.txt","r");
      5. Verificar se o arquivo foi aberto com sucesso;

      6. if (arquivo == NULL){
           printf("Error! opening file");
           return 1;
        }
      7. Percorcorrer o arquivo e ler os dados

        • Existem várias funções para ler os dados do arquivo, essas funções possuem os mesmos nomes que as funções já conhecidas, porém precedem de um 'f'.

        • As funções mais usadas são : fscanf, fgetchar, fgets.

        • A função fscanf é uma função que recebe 3 parâmetros, o primeiro é o ponteiro para o arquivo, o segundo são os parametros de leitura (%d pra numero, %s pra sting ...) e o ultimo os endereços para salvar os dados (igual já conhecemos com o scanf)

        for(int i = 0; i < 4; i++){
            fscanf(arquivo, "%s %lf",aluno[n].nome, &aluno[n].nota);
        }
        • Porém, caso não for conhecido a quantidade de linhas do arquivo, devemos ler os dados enquanto (While) não chegarmos ao final do arquivo (End of File).

        while(fscanf(arquivo, "%s %lf", aluno[n].nome, &aluno[n].nota)  != EOF){
            n++;
        }
      8. Operar com os dados
        • Vai do programador decidir o que fazer com os dados lidos.

        for(i = 0; i < n; i++){                                                
            printf("Aluno: %s\n",aluno[i].nome);
            printf("Nota: %.2lf\n",aluno[i].nota);                                                
        }
      9. Fechar o arquivo

      10. fclose(arquivo);

      Operação de Escrita

    • Para conseguir escrever os dados em um arquivo, basta novamente seguir os 3 primeiros passos mostrado anteriormente (com uma pequena mudança no passo 2) junto com esses novos passos:

      1. Abrir o arquivo no modo escrita

      2. arquivo = fopen("notas.txt","w"); 
      1. Percorrer os dados para salvar no arquivo;

        • A função utilizada para escrever em um arquivo de texto é a função fprintf, ou seja, a função printf imprime na tela uma mensagem, e a função fprintf imprime no arquivo a mensagem.

        • Vamos supor que as notas dos alunos tiveram um aumento de 10% e agora necessita de atualizar o arquivo

        for(i = 0; i < n; i++){  
            aluno[i].nota *= 1.1;                                              
            fprintf(arquivo,"%s %lf\n",aluno[i].nome,aluno[i].nota);
        }
    • O código completo você encontra aqui
    • Download Code

    Materiais Extras

    Falhas na Abertura de Arquivos

    Alguns dos erros que podem ocorrer na abertura de arquivos são:

    • O arquivo não existir no dietório corrente. Isso significa que o arquivo não existe no mesmo diretório em que o programa está rodando, mesmo que exista em outro diretório.

      Se queremos acessar um arquivo em um diretório diferente daquele do programa podemos passar o caminho completo do arquivo, ou um relativo em relação ao diretório que estamos. Por exemplo, se estivermos no diretório "/Desktop" e queremos acessar um arquivo "file.txt" dentro da pasta "files/", que fica no Desktop podemos fazer

      FILE* fd = fopen("files/file.txt", "r+");
                                                              

      Se a pasta "files/" ficar no diretório "Documents" podemos fazer assim:

      FILE* fd = fopen("../Documents/files/file.txt", "r+");
                                                              

      Lembre-se que ".." acessa o diretório acima do diretório atual. No caso, o diretório acima de Desktop é o diretório home.

    • Podemos não ter permissão de leitura ou escrita no arquivo, dependendo do lugar em que ele está salvo. Para resolver esse problema é necessário mudar as permissões do arquivo ou executar o programa como um usuário que tenha permissão sobre o arquivo.

    Fechando Arquivos Corretamente

    O tempo de acesso de disco é muito inferior ao tempo de acesso da RAM, por isso o computador procura fazer o menor número possível de operações em disco possível. Uma das técnicas que permitem isso é o buffering.

    Você irá aprender mais sobre isso na matéria de Organização de Arquivos, mas o importante agora é saber que quando o SO faz a ligação entre o ponteiro de arquivo e o arquivo ele também reserva um espaço na memória principal para operações com esse arquivo, i.e. o buffer do arquivo. Qualquer operação que fazemos com o arquivo, de leitura ou escrita, na verdade é realizada nesse buffer. Quando operações suficientes foram realizadas, ou se uma operação não pode ser feita no buffer, então o buffer é escrito no arquivo, e depois modificado na nova operação.

    De qualquer forma, quando o arquivo é fechado, usando a função fclose(), esse buffer é então escrito para o arquivo. Caso o arquivo não seja fechado e o programa termine, é possível que algumas operações feitas no buffer não sejam passadas para o arquivo, e sejam perdidas. Por isso é fundamental fechar os arquivos antes do final do programa.

    Se uma mudança é essencial para o arquivo e precisamos que ela seja passada ao arquivo imediatamente, podemos usar a função fflush(fd). Ela força a escrita do buffer no stream necessário.