Italo Info


Arquivos

Em todos os exemplos de programas mostrados nesse curso, até agora, os dados de variáveis são armazenados em memória principal. O que significa que, quando o programa é fechado, os dados são perdidos. Por isso, algumas vezes, necessitamos criar programas que armazenem dados em arquivos para que seu conteúdo persista, mesmo que o programa seja fechado ou o computador desligado.

Veja abaixo algumas das principais funções para manipulação de arquivos:

Função Descrição
fopen Abre um arquivo
fclose Fecha o arquivo
fgetc Lê um caractere do arquivo
fputc Escreve um caractere no arquivo
fgets Lê uma string do arquivo
fputs Escreve uma string no arquivo
fscanf Lê um conjunto de dados formatados do arquivo
fprint Escreve um conjunto de dados formatados em um arquivo
fwrite Lê dados binários
fread Escreve dados binários
fseek Muda o ponteiro de leitura para uma determinada posição do arquivo
feof Retorna se o ponteiro de registro está no final do arquivo.
Retorna 1 para verdade ou 0 para falso.
rewind Coloca a posição do ponteiro de registro no início do arquivo
remove Remove um arquivo pelo nome

Tipos de manipulação de arquivos

Quando um arquivo é aberto utilizando a função fopen são passados como parâmetro o nome do arquivo a ser criado/aberto e o tipo de manipulação do arquivo. Abaixo uma tabela com os tipos de manipulação:

Tipo (Leitura) Descrição
r Abre um arquivo texto para leitura.
r+ Abre um arquivo texto para leitura e escrita.
rb Abre um arquivo binário para leitura
r+b Abre um arquivo binário para leitura e escrita.

Tipo (Escrita) Descrição
w Abre o arquivo texto para escrita.
w+ Abre o arquivo texto para leitura e escrita. O arquivo é recriado se já existir.
wb Abre um arquivo binário para escrita.
w+b Se o arquivo binário para leitura e escrita. O arquivo é recriado se já existir.

Tipo (Append) Descrição
a Abre o arquivo para escrita no final do arquivo.
a+ Abre o arquivo para leitura e escrita no final do arquivo.
ab Abre um arquivo binário para escrita no final do arquivo.
a+b abre um arquivo binário para leitura e gravação no final do arquivo.

Atenção: Para abertura de um arquivo com tipo de manipulação iniciado com "r", é necessário que o mesmo já exista.

Atenção: Se um arquivo é aberto com tipo de manipulação iniciado com "w", um novo arquivo será criado.

Atenção: Se um arquivo é aberto com tipo de manipulação iniciado com "a", os dados escritos são inseridos no final do arquivo.

Arquivos texto

Abaixo, um exemplo que armazena duas frases em um arquivo de texto de nome "arq.txt":


#include <stdio.h>

int main() {
    FILE* arq;
    
    arq = fopen( "arq.txt", "w" );
    if ( arq == NULL ) {
        printf( "Nao foi possivel criar/abrir o arquivo arq.txt" );
        return 1;       
    }
    
    fputs( "Amor...!\n", arq );
    fputs( "Felicidade!", arq );
    fclose( arq );
    
    printf( "O arquivo arq.txt foi criado com sucesso...!" );
    
    return 0;
}

No programa acima, foi utilizada a função fopen para abrir ou criar um arquivo conforme os parâmetros passados para esta função. A função fopen, no exemplo acima, recebe como parâmetro o nome do arquivo e o tipo de manipulação. Perceba, também, que no exemplo acima, foram utilizadas as funções fputs e fclose. fputs serve para gravar uma string no arquivo e a função fclose serve para fechar o arquivo finalizando a leitura e/ou escrita e liberando os recursos alocados para manipulação do arquivo (Geralmente libera espaço em memória).


#include <stdio.h>

int main() {
    FILE* arq;
    char ch;
    
    arq = fopen( "arq.txt", "r" );
    if ( arq == NULL ) {
        printf( "Nao foi possivel abrir o arquivo arq.txt" );
        return 1;       
    }
    
    while( !feof( arq ) ) {
        ch = fgetc( arq );
        printf( "%c", ch );
    }
    
    fclose( arq );
   
    return 0;
}

O programa acima é bem simples. Ele abre um arquivo para leitura e, enquanto não for o fim do arquivo (verificação feita através da função feof) o programa executa o bloco de código do comando while. A cada iteração do comando while, um caractere é lido e armazenado na variável de nome ch e, logo depois, o valor lido é impresso na tela. Entenda que, a cada chamada da função fgetc o ponteiro de registro é posicionado no caractere seguinte no arquivo, até que esteja posicionado na posição de final do arquivo para que a função feof retorne VERDADE.

Arquivos binários

Algumas vezes precisamos escrever dados diferentes de texto em um arquivo. Para isso, podemos gravar os dados como valores binários (conjunto de bytes) em um arquivo de dados binário. Em arquivos binários podemos, por exemplo, armazenar conjuntos de variáveis como estruturas e recuperá-las depois.

fwrite e fread

A funções fwrite e fread são utilizadas para leitura e gravação de dados em um arquivo binário. As duas recebem 4 parâmetros, conforme mostrado abaixo:

fwrite( dados, quantidade de bytes, número de registros, ponteiro de arquivo );
fread ( dados, quantidade de bytes, número de registros, ponteiro de arquivo );

Onde:

  • dados: é uma referência a variável com os dados a serem armazenados no arquivo;

  • quantidade de bytes: normalmente é utilizada a função sizeof que retorna a quantidade de bytes necessários a alocação de determinado tipo de dados. Ex: sizeof( char ), sizeof( pessoa );

  • número de registros: é a quantidade de registros de tamanho( sizeof( tipo ) ) que devem ser lidos. Para gravação de um único registro, o valor desse parâmetro deve ser 1;

  • ponteiro de arquivo: é a variável do tipo FILE* carregada com a abertura do arquivo;

A cada vez que as funções fwrite e fread são executadas, o ponteiro de registro é incrementado em "quantidade de bytes".

fseek

A função fseek pode ser utilizada sempre que se deseje deslocar o ponteiro de registro para uma determinada posição para que se possa ler ou gravar a partir de tal posição. Abaixo o protótipo da função:

fseek( ponteiro de arquivo, deslocamento, tipo de deslocamento );

O ponteiro de arquivo é o retorno da função fopen. O deslocamento é a quantidade de bytes que o ponteiro de registro deve pular para ser posicionado na posição que se deseje e, o tipo de deslocamento pode ser algum dos valores constantes abaixo:

  • SEEK_SET: O ponteiro de registro é posicionado no início do arquivo e incrementado conforme o segundo parâmetro que é o deslocamento do ponteiro de registro;

  • SEEK_END: O ponteiro de registro é posicionado no fim do arquivo e decrementado conforme o segundo parâmetro;

  • SEEK_CUR: O ponteiro de registro é incrementado conforme o segundo parâmetro, a partir da posição atual;

Abaixo um exemplo de escrita em um arquivo binário:


#include <stdio.h>
#include <strings.h>

typedef struct spessoa {
    char nome[100];
    char telefone[100];
    int idade;
    float altura;
} pessoa;

int main() {
    char nome_arquivo[] = "arq.bin";
    FILE* arq;
    pessoa p, p2;
    
    arq = fopen( nome_arquivo, "wb" );
    if ( arq == NULL ) { 
        printf( "Erro na abertura do arquivo %s.", nome_arquivo );
        return 1;
    }

    printf( "Informe o nome: " );
    scanf( "%s", p.nome );
    printf( "Informe o telefone: " );
    scanf( "%s", p.telefone );
    printf( "Informe a idade: " );
    scanf("%d",&p.idade);
    printf( "Informe a altura: " );
    scanf("%f",&p.altura);
    
    fwrite( &p, sizeof( pessoa ), 1, arq );
    fclose( arq );
   
    printf( "\nDados gravados com sucesso!" );
    
    return 0;
}

No exemplo acima, o arquivo de nome "arq.bin" é aberto como binário para leitura e escrita. Caso o arquivo já exista, ele é recriado e seu conteúdo anterior é perdido, pois foi passado como segundo parâmetro para a função fopen a string "wb". Logo depois, os dados de uma pessoa são lidos e armazenados na variável de estrutura: pessoa. Então, a função fwrite é utilizada para gravação dos dados da pessoa armazenados na variável "pessoa".

Abaixo um exemplo de leitura do arquivo binário gravado no exemplo anterior:


#include <stdio.h>
#include <strings.h>

typedef struct spessoa {
    char nome[100];
    char telefone[100];
    int idade;
    float altura;
} pessoa;

int main() {
    char nome_arquivo[] = "arq.bin";
    FILE* arq;
    pessoa p;
    
    arq = fopen( nome_arquivo, "rb" );
    if ( arq == NULL ) { 
        printf( "Erro na abertura do arquivo %s.", nome_arquivo );
        return 1;
    }
        
    fread( &p, sizeof( pessoa ), 1, arq );
        
    printf( "\nNome gravado: %s", p.nome ); 
    printf( "\nTelefone gravado: %s", p.telefone ); 
    printf( "\nIdade gravada: %d", p.idade );   
    printf( "\naltura gravada: %f", p.altura ); 
        
    fclose( arq );
   
    return 0;
}

No exemplo acima, o arquivo de nome "arq.bin" é aberto como binário para leitura. É necessário que o arquivo exista ou, caso contrário, é mostrado um erro. Após o arquivo aberto, a função fread é utilizada para ler o registro de pessoa gravado pelo programa anterior. Abaixo, o exemplo de uma pequena agenda:


#include <stdio.h>
#include <stdlib.h>
#include <strings.h>

typedef struct spessoa {
    char nome[100];
    char tel[100];
    int idade;
    float altura;
} pessoa;

void insere( FILE* arq, char nome[], char tel[], int idade, float altura ) {
    pessoa p;
    strcpy( p.nome, nome );
    strcpy( p.tel, tel );
    p.idade = idade;
    p.altura = altura;
    
    fseek( arq, 0, SEEK_END );    
    fwrite( &p, sizeof( pessoa ), 1, arq );
}

void imprime_todos( FILE* arq ) {
    pessoa p;
    int tam;
    int cont = 1;
    
    rewind( arq ); // o mesmo que: fseek( arq, 0, SEEK_SET ); 
    
    tam = fread( &p, sizeof( pessoa ), 1, arq );
    while ( tam > 0 ) {        
        printf( "\nPessoa(%d)..", cont );
        printf( "\n  Nome....: %s", p.nome );
        printf( "\n  Tel.....: %s", p.tel );
        printf( "\n  Idade...: %d", p.idade );
        printf( "\n  Altura..: %.2f", p.altura );
        printf( "\n" );
        cont++;
        
        tam = fread( &p, sizeof( pessoa ), 1, arq );
    }
}

void recria_arquivo( FILE* arq, char nome_arq[] ) {
    fclose( arq );    
    remove( nome_arq );
    
    arq = fopen( nome_arq, "a+b" );
}

void menu() {
    system( "cls" );
    printf( "******** Agenda ********" );
    printf( "\n(1) Inserir" );
    printf( "\n(2) Mostrar todos" );
    printf( "\n(3) Recriar arquivo" );
    printf( "\n(0/ESQ) Sair" );
    printf( "\n***********************" );
    printf( "\n\n" );
}

int main() {
    char nome_arq[] = "arq.bin";    
    FILE* arq;
    
    int tam;
    char ch;
    char nome[100];
    char tel[100];
    int idade;
    float altura;
    
    arq = fopen( nome_arq, "a+b" );
    if ( arq == NULL ) {
        printf( "Erro na abertura do arquivo %s.", nome_arq );
        return 1;
    }
    
    ch = '\0';
    do {        
        menu();
        
        printf( "Informe a opcao: " );
        fflush( stdin );
        fflush( stdout );
        ch = getchar();
        printf( "\n" );

        switch ( ch ) {
            case '1':                
                printf( "Informe o nome: " );
                scanf( "%s", nome );
                printf( "Informe o telefone: " );
                scanf( "%s", tel );
                printf( "Informe a idade: " );
                scanf( "%d", &idade );
                printf( "Informe a altura: " );
                scanf( "%f", &altura );
                
                insere( arq, nome, tel, idade, altura );
                printf( "\nPessoa inserida com sucesso!" );
                fflush( stdin );
                fflush( stdout );
                getchar();                
                break;    
             case '2':
                printf( "***** Lista de pessoas *****" );
                printf( "\n\n" );
                imprime_todos( arq );
                fflush( stdin );
                fflush( stdout );
                getchar();
                break;
            case '3':
                recria_arquivo( arq, nome_arq );
                printf( "Arquivo recriado com sucesso...!" );
                fflush( stdin );
                fflush( stdout );
                getchar();
                break;
        }                       
    } while ( ch != '0' && (int)ch != 27 );
           
    fclose( arq );
   
    return 0;
}

No exemplo acima, foram criadas três funções: insere, imprime_todos e recria_arquivo. Preste atenção a função insere onde é chamada a função fseek. A função fseek é chamada para posicionar o ponteiro de registro no final do arquivo para, então, inserir os dados no fim do arquivo. Preste atenção também que na função imprime_todos também é chamada a função rewind para posicionar o ponteiro de registro no início do arquivo. Outro ponto que merece atenção é a chamada da função remove na função recria_arquivo. O arquivo é removido pelo nome e, depois, recriado e aberto para leitura e escrita no final do arquivo. Repare nas chamadas a função fflush para stdin e stdout. Esta função limpa o buffer de leitura e escrita na entrada e saída padrão para evitar um comportamento estranho nas leituras.