Tratamento de ficheiros
O motivo pelo qual se torna fundamental a utilização de ficheiros resulta da necessidade de perpetuar os dados para além do ciclo de vida de um programa.
Isto significa que os dados armazenados nas variáveis criadas pela execução do programa, residentes na memória principal que, como se sabe, é de natureza volátil, apenas se encontram disponíveis enquanto o programa está em execução. Este facto representa uma clara limitação à longevidade dos dados que, em última instância, ficam dependentes de termos o nosso programa em execução vinte e quatro horas por dia, pois caso o programa seja terminado ou o computador desligado tudo voltará ao ponto de partida.
Uma solução possível consiste na utilização de ficheiros, que pelo facto de serem armazenados em suportes secundários, não ficam dependentes do programa estar em execução ou de termos o nosso equipamento ligado.
Em termos de tipos de ficheiros, e uma vez que vamos abordar os modos texto/binários, vamos considerar duas grandes áreas:
• Ficheiros de Texto: Serão aqueles que contêm caracteres percetíveis para o ser humano. São caracteres da tabela ASCII como algarismos, letras do alfabeto, caracteres de acentuação, pontuação e outros como é o caso do caracter “New Line” que, apesar de não ser visível, representa uma mudança de linha.
• Ficheiros binários: Nos ficheiros binários não é bem a representação lógica dos seus bytes em caracteres que está em causa, mas o armazenamento da informação no seu formato mais elementar, ou seja, em sequências de uns e zeros.
Definição de ficheiro
Um ficheiro é uma coleção de dados ou informação que é representada por um nome que se designa por “nome do ficheiro“.
Praticamente toda a informação armazenada no computador deve estar armazenada num ficheiro.
Existem muito tipos de ficheiros, entre os quais:
• de dados;
• de texto;
• de programa;
• de diretoria;
Fases do tratamento de ficheiros
Sempre que pretendemos trabalhar com ficheiros em C, seja para ler dados, seja para escrever dados, executamos as seguintes fases:
1. Abrir o ficheiro, usando o nome do ficheiro
2. Ler e/ou escrever dados
3. Fechar o ficheiro.
A abertura de um ficheiro é feita com a função fopen.
Ler/ escrever dados no ficheiro é feita com um conjunto de funções que veremos mais à frente.
O fecho do ficheiro é feito com a função fclose.
Abertura de um ficheiro com fopen
A função fopen() é a função em C para abrir ficheiros.
A sua sintaxe é a seguinte:
FILE * fopen ( const char * filename, const char * mode );
Esta função aceita dois parâmetros: o nome e o modo de abertura. Retorna um ponteiro para a estrututa FILE * caso a abertura seja bem sucedida, ou NULL no caso contrário.
NOTA: o ponteiro retornado pelo função fopen é usado em todas as funções de leitura/escrita subsequentes, incluindo a função fclose pois ele identifica o ficheiro.
Os modos de abertura podem ser os seguintes:
Modo | Descrição |
“r” | Abre o ficheiro para leitura. O ficheiro tem de existir. |
“w” | Cria um novo ficheiro para escrita. Se o ficheiro já existe é não existir é criado um novo. |
“a” | Append. Permite escrever dados no final do ficheiro. Se o ficheiro não existir é criado um novo. |
“r+” | Atualização. Abre o ficheiro para leitura e/ou escrita. O ficheiro tem de existir. |
“w+” | Cria um novo ficheiro para leitura e/ou escrita. Se o ficheiro existir, ele é apagado e criado um novo ficheiro vazio. |
“a+” | Abre um ficheiro para leitura e append. As operação de escrita são feitas no final do ficheiro, preservando os dados já existentes. O ficheiro é criado se não existir. |
NOTA: os modos de abertura acima referem-se todos a ficheiros de texto. Para se abrir um ficheiro binário num dos modos acima referidos basta acrescentar o caractere ‘b’ na string do modo de abertura: “rb”, “wb”, “ab”, “rb+”, “wb+”, “ab+”. Nos modos constantes no quadro, também se pode explicitar que se pretende abrir um ficheiro de texto, acrescentando o caractere ‘t’.
Exemplo:
FILE *fp = fopen("ABC.txt", "r");
if(fp){ // verifica se o ficheiro foi aberto com sucesso
printf("\nABC.txt aberto com sucesso\n");
}else{
printf("\nErro na abertura do ficheiro\n");
}
Fechar um ficheiro com fclose
No final, quando já não necessitarmos mais do ficheiro devemos sempre fechá-lo. O fecho de um ficheiro é feito usando a função fclose().
A função fclose() tem a seguinte assinatura:
int fclose ( FILE * fp );
Aceita como argumento o ponteiro para o ficheiro. Retorna 0 (zero) se o fecho fôr bem sucedido e EOF no caso contrário.
Exemplo:
FILE *fp = fopen("ABC.txt", "r");
if(fp){ // verifica se o ficheiro foi aberto com sucesso
//... manipula o ficheiro
fclose(fp);
}else{
printf("\nErro na abertura do ficheiro\n");
}
Leitura/escrita de ficheiros
Ficheiros de texto
Um ficheiro de texto é um ficheiro que contém caracteres da tabela ASCII, organizados em linhas de texto, ou seja sequências de caracteres que terminam pelo caractere fim de linha.
O tratamento de ficheiros de texto pode ser feito de 4 modos:
- Caractere a caractere
- Por strings
- Por blocos de texto
- Escrita/Leitura formatada
O conjunto de funções para ler/escrever dados nestes 4 modos pode ser visto no quadro seguinte:
Função | Modo |
fputc() | Caractere |
fgetc | Caractere |
fputs | Strings |
fgets | Strings |
fprintf | Formatada |
fscanf | Formatada |
fputc() – escreve um caractere no ficheiro
Assinatura:
int fputc ( int character, FILE * fp );
Escreve um caractere no ficheiro e avança o indicador de posição.
O caractere é escrito na posição atual, indicado pelo indicador de posição e, depois, este avança uma posição.
Parâmetros:
character: caractere a ser escrito no ficheiro.
*fp: apontador para o ficheiro
Valor de retorno:
Se não houver erros, a função fputc() retorna o caractere que foi escrito. Caso contrário retorna EOF, e o indicador de erro é activado (ver ferror()).
Exemplo:
#include <stdio.h>
int main ()
{
FILE * pFile;
char c;
pFile = fopen ("alfabet0.txt","w");
if (pFile!=NULL){
for (c = 'A' ; c <= 'Z' ; c++){
fputc ( (int) c , pFile );
}
}
fclose (pFile);
return 0;
}
fgetc() – lê um caractere do ficheiro
Assinatura:
int fgetc ( FILE * fp );
Devolve o caractere do ficheiro que está na posição corrente do indicador de posição. O indicador de posição é depois avançado uma posição.
Parâmetros:
*fp: apontador para o ficheiro
Valor de retorno:
O caractere lido. Este caractere é devolvido como int. Se o fim de ficheiro (EOF) é alcançado ou um erro ocorrer durante a leitura, a função devolve EOF e o correspondente indicador de erro ou de fim de ficheiro é ativado. Pode-se usar as funções ferror() ou feof() para verificar se ocorreu um erro ou o fim de ficheiro foi alcançado.
Exemplo:
#include <stdio.h>
int main()
{
FILE * fp;
int c;
int n = 0;
fp=fopen ("myfile.txt","r");
if (fp==NULL){
printf("Erro na abertura do ficheiro\n");
}else{
do {
c = fgetc (fp);
printf("%c", c);
} while (c != EOF);
fclose (fp);
}
return 0;
}
fputs() – escreve uma string no ficheiro
Assinatura:
int fputs ( const char * str, FILE * fp );
Escreve a string apontada por *str no ficheiro.
A função copia todos os caracteres a partir do caractere no endereço referenciado pelo apontador *str até encontar o caracter null (‘\0’). O caractere ‘\0’ não é escrito.
Parâmetros:
*str: string (ou array de caracteres) terminada por ’\0’.
*fp: apontador para o ficheiro
Valor de retorno:
Se a escrita fôr bem sucedida a função devolve um valor maior que zero. Caso contrário, devolve EOF.
Exemplo:
#include <stdio.h>
int main ()
{
FILE * pFile;
char frase [256];
printf ("Escreva uma frase: ");
fgets (frase,255,stdin);
pFile = fopen ("log.txt","a");
fputs (frase,pFile);
fclose (pFile);
return 0;
}
fgets() – lê uma string do ficheiro
Assinatura:
char * fgets ( char * str, int num, FILE * fp );
Lê caracteres do ficheiro e armazena-os na string com o endereço especificado por *str até que num-1 caracteres sejam lidos ou seja encontrado o caractere newline ou EOF, o que acontecer primeiro. No caso da leitura terminar por a função ter encontrado o caractere newline, este é incluído na string str. No final da leitura, o caractere ‘\0’ é posto no fim da string str.
Parâmetros:
*str: apontador para um array de caracteres onde a string lida é armazenada.
num: numero máximo de caracteres a ser lido, incluindo o caractere ‘\0’.
*fp: apontador para o ficheiro
Valor de retorno:
Se a leitura fôr bem sucedida, a função retorna o parâmetro str.
No caso de ser encontrado EOF (fim de ficheiro) e nenhum caractere tiver sido lido, a string str permanece inalterada e é devolvido NULL.
Se ocorrer um erro, é devolvido NULL.
Usar as funções ferror() ou feof() para confirmar se houve um erro ou o fim de ficheiro tiver sido alcançado.
Exemplo 1:
#include <stdio.h>
int main ()
{
FILE * pFile;
char mystring [200];
pFile = fopen ("log.txt" , "r");
if (pFile == NULL){
printf ("Erro na abertura do ficheiro!");
} else {
if ( fgets (mystring , 100 , pFile) != NULL ){
puts (mystring);
}
fclose (pFile);
}
return 0;
}
Exemplo 2:
#include <stdio.h>
int main ()
{
FILE * pFile;
char mystring [100];
pFile = fopen ("log.txt" , "r");
if (pFile == NULL){
printf ("Erro na abertura do ficheiro!");
} else {
while(!feof(pFile)) {
if(fgets (mystring , 100 , pFile)){
puts (mystring);
}
}
fclose (pFile);
}
return 0;
}
fprintf() – escreve output formatado no ficheiro
Assinatura:
int fprintf ( FILE * fp, const char * format, ... );
Escreve uma sequência de dados formatados no ficheiro. A formatação é especificada no parâmetro format.
Parâmetros:
*fp: apontador para o ficheiro.
*format: contém o texto a ser escrito no ficheiro, acompanhado por atributos de formatação.
Os atributos de formatação são os constantes do quadro seguinte:
atributos | output | exemplo |
c | caractere | h |
d ou i | inteiro com sinal | 46 |
e | notação cientifica | 3.9265e+2 |
E | notação cientifica | 3.9265E+2 |
f | numero real (float) | 78.6 |
g | %e ou %f | 78.6 |
G | %E ou %f | 78.6 |
o | octal com sinal | 610 |
s | string de caracteres | ola mundo |
u | inteiro sem sinal | 8472 |
x | inteiro hexadecimal sem sinal | 6da |
X | inteiro hexadecimal sem sinal | 6DA |
p | endereço de apontador | B800:0000 |
n | nada a imprimir | |
% | % seguido de % escreve % |
Valor de retorno:
Caso a escrita seja bem sucedida, retorna o número de caracteres escritos. Caso contrário, retorna um número negativo.
Exemplo:
#include <stdio.h>
int main ()
{
FILE * pFile;
int n;
char name [100];
pFile = fopen ("nomes.txt","w");
for (n=0 ; n<3 ; n++)
{
puts ("Escreva um nome: ");
gets (name);
fprintf (pFile, "Nome %d %s\n",n,name);
}
fclose (pFile);
return 0;
}
fscanf() – lê input formatado do ficheiro
Assinatura:
int fscanf ( FILE * stream, const char * format, ... );
Lê dados do ficheiro e armazena-os de acordo com a string de formatação especificada no parâmetro format.
Parâmetros:
*fp: apontador para o ficheiro.
*format contém o texto a ser escrito no ficheiro, acompanhado por atributos de formatação.
Valor de retorno:
Se a leitura fôr bem sucedida, retorna o número de items lidos. Caso contrário, devolve EOF.
Exemplo:
#include <stdio.h>
int main ()
{
char str [80];
float f;
FILE * fp;
fp = fopen ("myfile.txt","w+");
fprintf (fp, "%f %s", 3.1416, "PI");
//reposiciona o indicador de posição no inicio do ficheiro:
rewind (fp);
fscanf (fp, "%f", &f);
fscanf (fp, "%s", str);
fclose (fp);
printf ("%f %s \n",f,str);
return 0;
}
Ficheiros binários
Um ficheiro binário é um ficheiro em que os dados são escritos na forma binária, não percetíveis por seres humanos. Ao contrário dos ficheiros de texto, em que os dados são escritos em caracteres, ou linhas de strings, ou blocos de strings, os dados nos ficheiros binários são escritos em registos.
Geralmente, cada registo é uma estrutura, qualquer tipo de estrutura. Após ser aberto pode-se ler uma estrutura do ficheiro, escrever uma estrutura no ficheiro, ou procurar por uma determinada estrutura no ficheiro, através da sua posição.
Os ficheiros binários permitem ainda que se navegue por entre os registos.
Funções usadas no tratamento de ficheiros binários
Função | Descrição |
fread() | Lê qualquer tipo de dados |
fwrite() | Escreve qualquer tipo de dados |
fseek() | Move o indicador de posição do ficheiro para uma determinada posição |
fread() – lê blocos de dados do ficheiro
Assinatura:
int fread ( void * ptr, int size, int count, FILE * fp );
Lê blocos de registos do ficheiro e armazena-os no buffer indicado por *ptr.
Uma operação de leitura lê o bloco de dados para onde o indicador de posição do ficheiro aponta (quando o ficheiro é aberto o indicador de posição do ficheiro aponta para zero). Após a leitura, o indicador de posição avança para o próximo bloco de dados (por exemplo, a próxima estrutura).
Parâmetros:
*ptr: apontador para o bloco de memória onde o bloco de dados lido é armazenado.
size: tamanho em bytes de cada bloco lido.
count: número de blocos a ler.
*fp: apontador para o ficheiro.
Valor de retorno:
O número de blocos lidos. Se este número diferir do parâmetro count, ocorreu um erro ou o fim de ficheiro (EOF) foi atingido. Pode-se usar as funções ferror() ou feof() para determinar isso.
Exemplo:
#include <stdio.h>
struct Aluno{
int nrAluno;
char nome[50];
};
int main(){
struct Aluno aluno;
FILE * fp;
fp = fopen ( "alunos.bin" , "rb" );
if(fp){
fread(&aluno, sizeof(aluno), 1, fp);
while(!feof(fp)){
printf("\nNr aluno: %d\n", aluno.nrAluno);
printf("\nNome: %s\n", aluno.nome);
fread(&aluno, sizeof(aluno), 1, fp);
}
fclose(fp);
}else{
puts("Erro na abertura do ficheiro!");
}
}
fwrite() – escreve blocos de dados no ficheiro
Assinatura:
int fwrite(const void *ptr, int size, int count, FILE * fp );
Escreve um bloco de dados composto por um número de elementos especificado no parâmetro count.
Uma operação de escrita, escreve na posição indicada pelo indicador de posição do ficheiro.
Após a escrita, o indicador de posição é movido para o próximo bloco.
Parâmetros:
*ptr: apontador para o bloco de memória a ser escrito no ficheiro.
size: tamanho em bytes de cada bloco a escrever.
count: número de blocos a escrever.
*fp: apontador para o ficheiro.
Valor de retorno:
O número de blocos escritos. Se este número diferir do parâmetro count, ocorreu um erro.
Exemplo:
#include <stdio.h>
#include <string.h>
struct Aluno{
int nrAluno;
char nome[50];
};
int main(){
struct Aluno aluno;
aluno.nrAluno = 1;
strcpy(aluno.nome, "Ana");
FILE * fp;
fp = fopen ( "alunos.bin" , "wb" ); if(fp){
int ret = fwrite(&aluno, sizeof(aluno), 1, fp);
if(ret == 1){
puts("registo escrito");
}else{
puts("erro na escrita!");
}
fclose(fp);
}
}
fseek() – reposiciona o indicador de posição do ficheiro
Assinatura:
int fseek ( FILE * fp, long int offset, int origin );
A função fseek() reposiciona o indicador de posição do ficheiro num qualquer byte do ficheiro.
A nova posição é definida indicando um deslocamento (offset) a partir de uma posição de origem (origin).
Parâmetros:
*fp: apontador para o ficheiro.
offset: deslocamento: número de bytes a partir da posição origin.
origin: posição a partir da qual o deslocamento é adicionado. É especificado por uma
das seguintes constantes:
SEEK_SET | Início do ficheiro | |
SEEK_CUR | Posição actual do indicador de posição | |
SEEK_END | Fim do ficheiro |
Valor de retorno:
Se o reposicionamento fôr bem sucedido, retorna zero. Caso contrário, retorna um valor diferente de zero.
Exemplo:
#include <stdio.h>
#include <stdlib.h>
struct Fullname {
char firstName[40];
char lastName[10];
} info;
int main(void){
FILE *fp;
if((fp=fopen("test", "rb")) == NULL) {
printf("Cannot open file.\n");
exit(1);
}
int client_num = 10;
/* vai para a estrutura pretendida */
fseek(fp, client_num*sizeof(struct Fullname), SEEK_SET);
/* lê os dados para memória */
fread(&info, sizeof(struct Fullname), 1, fp);
fclose(fp);
}