Curso de C
Arquivos
Você está em: MarMSX >> Cursos >> C
A memória do computador é volátil, e todos os dados contidos nela é perdido após o desligamento do computador.
Se desejarmos manter os dados de uma aplicação vivos após o desligamento, como por exemplo, o resultado de um jogo ou os nomes de uma agenda telefônica, teremos que salvá-los em disco.
Existem dois formatos de arquivos: o formato texto e o formato binário.
Em arquivos no formato texto, os códigos armazenados serão interpretados como valor de entrada na tabela ASCII, de maneira a formar um texto. Assim, por exemplo, o código 65 significa a letra 'A' maiúscula. Além disso, os arquivos no formato texto são divididos em linhas, onde um caracter com um código determinado indica o fim de uma linha.
Em arquivos binários, os códigos são a representação direta dos dados, em seu formato original. Por exemplo, um inteiro será armazenado em disco como é armazenado na memória, ou seja, com 2 bytes por número. Já o double, em seu formato de 8 bits.
Fica evidente que o programador deverá conhecer a disposição dos dados e seus respectivos tipos na hora de recuperar a informação no disco. Caso contrário, poderemos recuperar dados de um número double em um variável do tipo inteiro, por exemplo, causando inconsistência nos dados.
A função em C que abre um arquivo para trabalhar é:
fopen(<nome_do_arquivo>, <parâmetros_do_arquivo>);
No qual retorna um ponteiro para uma variável do tipo arquivo (FILE).
Os parâmetros de abertura de um arquivo são:
- r - somente leitura.
- w - leitura e escrita.
- a - append.
- b - arquivo binário.
- t - arquivo texto.
Arquivos do Tipo Texto
Gravação de Dados em Disco
A função fprintf é utilizada quando deseja-se enviar uma string para um arquivo de saída. Por exemplo, pode-se gravar uma frase em um arquivo texto da seguinte maneira:
#include <stdio.h>
FILE *fp;
void main(void)
{
fp = fopen("arquivo.txt","wt");
fprintf(fp, "O MSX ainda vive!");
fclose(fp);
}
Leitura de Dados em Disco
A leitura de textos é feita através da função "getc". Essa função lê caractere a caractere. Entretanto, é desejável ler o texto linha a linha.
O programa abaixo implementa uma função de leitura de linha, a "readln".
#include <stdio.h>
FILE *fp;
char linha[255];
void readln(FILE *fp, char *linha)
{
char c;
int p=0;
while ((c = fgetc(fp)) != EOF)
{
if (c == '\n')
break;
linha[p++] = c;
}
}
void main(void)
{
fp = fopen("arquivo.txt","rt");
readln(fp, linha);
fclose(fp);
printf("%s\n", linha);
}
Leitura formatada
Podemos utilizar a função fscanf para realizar uma leitura formatada dos dados.
Por exemplo, seja o arquivo formatado a seguir "dados.txt", contendo uma lista de objetos.
Cachorro
Gato
Mesa
Cadeira
Salão de jogos
O programa a seguir irá ler o arquivo e escrever os dados na tela.
#include <stdio.h>
main()
{
FILE *fp;
char texto[10];
fp = fopen("dados.txt", "rt");
while (fscanf(fp, "%s", texto) != EOF)
printf("%s\n", texto);
}
Saída:
Cachorro
Gato
Mesa
Cadeira
Salão
de
jogos
A opção de formatação "%s" lê uma string até encontrar um espaço em branco. Para solucionar o problema do "Salão de jogos" escrito em três linhas, podemos utilizar a opção de formatação de string [] ou [^]. A primeira opção irá conter uma lista de caracteres aceitos, enquanto que a segunda uma lista de caracteres não aceitos, e que quando encontrados, termina a formação da string. A esses caracteres que param a formação de uma string chamamos de delimitadores.
Dessa forma, trocamos a linha:
while (fscanf(fp, "%s", texto) != EOF)
Por:
while (fscanf(fp, "%[^\n]\n", texto) != EOF)
No caso do nosso arquivo, o delimitador é o caractere de nova linha "\n". O delimitador deverá ser repetido logo após os colchetes, senão o programa não pára de ler a mesma linha. No caso de arquivos com a terminação de linha no formato do MSX e Windows, utilizar a formatação "%[^\r]\r\n".
Arquivos do Tipo Binário
Os arquivos de tipo binário podem ser lidos byte a byte ou por blocos.
Observa-se que a leitura byte a byte deve ser evitada, pois o acesso ao disco repetidamente é mais caro (leva mais tempo) do que o acesso único, realizado na transferência de dados por bloco.
Assim, temos as seguintes funções:
- fgetc / fputc - lê / grava dados em disco, byte a byte.
- fread / fwrite - lê / grava dados em disco, em bloco.
Sintaxe do fread / fwrite:
fread(<variavel>, <tamanho>, <num_leituras_sequenciais>, <arquivo>);
fwrite(<variavel>, <tamanho>, <num_leituras_sequenciais>, <arquivo>);
Onde:
- variavel - ponteiro para um variável que recebe / transfere dados para o disco. Pode ser do tipo primitiva, vetor ou struct.
- tamanho - quantidade de bytes transferidos.
- num_leituras_sequenciais - número de vezes que a operação de leitura / gravação é feita repetidadmente.
- arquivo - ponteiro para o arquivo em disco.
Exemplo de abertura de arquivo binário em bloco:
#include <stdio.h>
FILE *fp;
char buffer[1024];
void main(void)
{
fp = fopen("exemplo.bin","rb");
fread(buffer, 1024, 1, fp);
fseek(fp, 40, SEEK_SET);
fclose(fp);
}
Obs: o comando fseek move o ponteiro no arquivo (ver mais detalhes no capítulo de arquivos do Pascal). Sintaxe:
fseek(<arquivo>, <offset>, <referência>);
Onde:
- arquivo - ponteiro para o arquivo em disco.
- offset - é o espaço a avançar, a partir da referência.
- referência - posição de referência no disco. Pode ser:
- SEEK_SET - Início.
- SEEK_CUR - Posição atual.
- SEEK_END - Final.
Obs: caso as constantes SEEK_SET, SEEK_CUR e SEEK_END não funcionem, substitua pelos valores:
- SEEK_SET - substitua por 0.
- SEEK_CUR - substitua por 1.
- SEEK_END - substitua por 2.
Calculando o tamanho de um arquivo
Para calcular o tamanho de um arquivo em C, devemos posicionar o cursor de arquivo no final dele e calcular sua posição através da função ftell().
O programa a seguir calcula o tamanho em bytes de um arquivo chamado "arquivo.txt".
#include <stdio.h>
main()
{
FILE *fp;
int tamanho;
fp = fopen("arquivo.txt","rb");
fseek(fp, 0, 2);
tamanho = ftell(fp);
printf("Tamanho do arquivo: %d\n", tamanho);
fclose(fp);
}
O valor 2 na função fseek() é a constante SEEK_END.
Trabalhe com os dados de arquivos sempre na memória do micro
Um arquivo de disco é o armazenamento permanente de dados, que são transferidos a partir de uma memória volátil (RAM). Isto é feito para que os dados não sejam perdidos quando o micro é desligado.
Trabalhamos sempre com os dados na memória RAM do micro, pois é o tipo de memória mais rápido e de uso eficiente do sistema. Dessa forma, quando necessitamos utilizar esses dados, devemos sempre copiá-los do disco de volta para a memória RAM. Se os dados couberem por completo na memória, carregue-os todos de uma vez em um vetor. Caso o arquivo seja muito grande, carregue uma parte do arquivo por vez em um vetor, com um tamanho que caiba na memória.
Nunca crie sistemas onde o acesso aos dados de arquivo seja feito byte a byte, com uso abusivo da função fseek().
No exemplo a seguir, iremos carregar um arquivo binário de MSX, modificar seu cabeçalho e salvar com o nome de "arqmod.bin". O arquivo deverá ser menor ou igual a 1024 bytes.
#include <stdio.h>
unsigned char buffer[1024];
main(int argv, char * argc[])
{
FILE *fp;
int tamanho;
if (argv!=2)
{
printf("Erro. Nome de arquivo nao informado.\n");
return;
}
// Arbre arquivo para leitura
fp = fopen(argc[1],"rb");
if (fp == 0)
{
printf("Erro. arquivo %s nao encontrado.\n", argc[1]);
return;
}
// Calcula o tamanho
fseek(fp, 0, 2);
tamanho = ftell(fp);
if (tamanho > 1024)
{
printf("Arquivo muito grande. Maximo 1024 bytes.\n");
return;
}
// Tranfere arquivo para memoria
fseek(fp, 0, 0); // Volta inicio
fread(&buffer, tamanho, 1, fp);
// Fecha arquivo aberto para leitura
fclose(fp);
// Altera header (endereco de execucao)
buffer[5] = 0x00; buffer[6] = 0xCD;
// Salva um novo arquivo - nome: arqmod.bin
fp = fopen("arqmod.bin","wb");
fwrite(&buffer, tamanho, 1, fp);
fclose(fp);
}
Execute o programa colocando como parâmetro o nome do arquivo. Por exemplo, se o nome do programa for "MODIFICA.COM" e do arquivo binário "FILE.BIN", digite no MSX-DOS:
A> modifica file.bin
Nota importante: observe o comando "fseek(fp, 0, 0);" antes da leitura dos dados. Como havíamos movido o cursor para o fim do arquivo para calcular seu tamanho, temos que tomar o cuidado de retornar o ponteiro ao incio do arquivo, sob pena de não carregarmos os dados desse arquivo.
Gravando e Recuperando Estruturas
O programa a seguir cria uma ficha de alunos e a salva no formato binário.
#include <stdio.h>
#include <string.h>
struct mod_ficha
{
char nome[10];
int idade;
int serie;
float cr;
} ficha;
FILE *fp;
void main(void)
{
// Preenche ficha
strcpy(ficha.nome, "Catarina");
ficha.idade = 12;
ficha.serie = 6;
ficha.cr = 8.9;
fp = fopen("ficha.fch","wb");
fwrite(&ficha, sizeof(struct mod_ficha), 1, fp);
fclose(fp);
}
Resultado, editando o arquivo "ficha.fch" em um editor hexadecimal.
Bytes | ASCII
------------------------------------+-------------
43 61 74 61 72 69 6E 61 00 00 00 00 | Catarina....
0C 00 00 00 06 00 00 00 66 66 0E 41 | ........ff.A
Recuperando o arquivo:
#include <stdio.h>
#include <string.h>
struct mod_ficha
{
char nome[10];
int idade;
int serie;
float cr;
} ficha;
FILE *fp;
void main(void)
{
fp = fopen("ficha.fch","rb");
fread(&ficha, sizeof(struct mod_ficha), 1, fp);
fclose(fp);
printf("Nome: %s\n", ficha.nome);
printf("Idade: %d\n", ficha.idade);
printf("Série: %d\n", ficha.serie);
printf("Coeficiente de rendimento: %.2f\n", ficha.cr);
}
Saída:
Nome: Catarina
Idade: 12
Série: 6
Coeficiente de rendimento: 8.90