CEFETES – Serra - Programação II – Mateus Costa
Notas de Aula - Arquivos
1
Arquivos em C
O objetivo deste capítulo e permitir a construção de programas que armazenem dados de
forma permanente em dispositivos de armazenamento secundário, tais como disquetes e
discos rígidos.
1.1
Definições
Sistema de arquivos em C Æ é o conjunto de funções da linguagem que podem ler,
escrever e manipular dados em dispositivos periféricos.
Stream e Arquivos Æ Stream é uma abstração do dispositivo periférico. O dispositivo real é
o arquivo. Em C o termo Stream identifica logicamente um dispositivo periférico qualquer
que permita a leitura e/ou gravação de dados.
No entanto neste texto usaremos o termo arquivo para nos referirmos aos arquivos gravados
em memória secundária
Arquivos de dados armazenados em memória Secundária Æ permitem o armazenamento
permanentemente dos dados.
A linguagem C provê um conjunto completo de funções para manipulação de arquivos em disco.
1.2 Tipos de arquivos
Existem dois tipos básicos de arquivos, nos quais se baseiam programas que manipulam
arquivos. Em C esses tipos de arquivos são chamados de arquivos texto e arquivos binários.
Arquivo texto - É um arquivo cujo conteúdo é baseado em uma seqüência de caracteres que
formam linhas determinadas por um caracter de nova linha ( “ \ n ”). Dentro destes arquivos
podem ser gravados apenas dados em forma de texto. Ou seja, não poderemos gravar, por
exemplo, um valor numérico nestes arquivos, a não ser que este valor seja transformado em
seqüências de caracteres. Esta forma de organização de arquivos e a mais simples.
Funções em C
Funções são subprogramas
chamados
que
quando
Arquivo texto
Arquivos binários – Arquivos binários tem o seu conteúdo baseado em uma estrutura ou
dado que respeita um determinado tipo de dado. Este tipo de dado pode ser um tipo simples
(int, float, char) ou um tipo estruturado como registro (struct). Portanto esses arquivos são
construídos como uma seqüência de bytes respeitando uma determinada estrutura. Esses
arquivos podem ser visualizados como um tabela onde cada linha possui um determinado
conjuntos de campos (colunas). Cada linha dessa tabela possuirá também um número que
identificará sua posição no arquivo. Por essas características, tais arquivos têm a vantagem de
permitir o acesso aleatório a posições.
Nome
Telefone idade
Maria
45545
21
João
87878
30
Paula
78867
22
José
565675
20
...
...
...
Visualização abstrata de um arquivo
binário organizado a partir de uma
struct contendo os campos nome,
telefone e idade.
1.3
Ferramentas para manipulação de arquivos
1.3.1 Estrutura de controle FILE –
A linguagem C utiliza esta estrutura para criarmos um ponteiro para o arquivo.
Este ponteiro irá conter o endereço da estrutura FILE criada que manterá várias informações
sobre o arquivo, como o seu nome, posição atual, etc. Estas informações são usadas pelas
funções do sistema de arquivos em C.
Para ler ou escrever arquivos, qualquer programa precisa usar ponteiros de arquivos.
Declaração de um ponteiro de arquivo
FILE *fp;
FILE – é o nome da estrutura (tipo de dado)
O * define que a variável fp é uma variável do tipo ponteiro para FILE.
1.3.2 Abrindo um arquivo
abrir um arquivo significa criar uma associação entre o arquivo físico (na memória
secundária) e o programa, através do ponteiro de arquivo.
Para tanto usa-se a função fopen cujo protótipo é mostrado a seguir. O protótipo de uma
função indica como a função foi definida, qual o tipo de dado que ela retorna e quais os
parâmetros da função. Os parâmetros são indicados entre os parênteses da função. Por
exemplo no lugar do parâmetro nomearq da função fopen, deve ser informado o nome do
arquivo a ser aberto, através de uma variável do tipo ponteiro para char. Ainda não falamos
de ponteiros para char. No entanto você já está acustumado a usá-los. Trata-se de uma string,
de um variável do tipo cadeia de caracteres (ex. char str[30]) ou de uma constante do tipo
string (ex. “arquivox.txt”).
O valor de retorno de uma função deve ser atribuído a uma variável do mesmo tipo da
função. No caso, fopen() é definida como um FILE *, ou seja, um ponteiro para FILE. Assim
sendo, quando fopen é chamada deve ser feita com a atribuição do seu valor de retorna a uma
variável do tipo FILE *.
Ex: FILE *arq;
arq = fopen(“arquivox.txt”, “w);
Usar o comando de atribuição entre uma variável e uma função indica que o valor de
retorno da função está sendo atribuído a variável.
Parâmetro indicando o nome físico do arquivo
FILE *fopen ( const char * nomearq, const char *modo)
Significa que
fopen retorna
um ponteiro
de arquivo
parâmetro do tipo string que determina o modo
como o arquivo será aberto
nomearq é um ponteiro (nome de string) para uma cadeia de caracteres que indica um nome
válido de um arquivo, podendo indicar o seu caminho.
modo é um ponteiro para uma cadeia de caracteres que indica o modo como o arquivo será
aberto.
Esta string é construída utilizando-se os caracteres da tabela abaixo:
MODO
r
w
a
rb
wb
ab
r+
w+
a+
r+b
w+b
a+b
SIGNIFICADO
Abre um arquivo texto para leitura
Cria um arquivo texto para escrita ou sobrescreve caso o arquivo
exista
Anexa a um arquivo texto: abre para escrita no final do arquivo texto
ou caia um novo arquivo caso o mesmo não exista
Abre um arquivo binário para leitura
Cria um arquivo binário para escrita
Abre um arquivo binário para escrita no final do arquivo ou cria um
novo caso o mesmo não exista
Abre um arquivo texto para leitura e escrita
Cria um arquivo texto p/ leitura e escrita ou sobrescreve caso o
arquivo já exista
Abre um arquivo e cria um novo para leitura e escrita no final do
arquivo
Abre um arquivo binário para leitura/escrita
Cria um arquivo binário para leitura/escrita
Abre um arquivo binário para leitura e escrita no final do mesmo
Exemplo: Suponha o arquivo teste.txt. O programa abaixo pode ser usado para abrí-lo para
escrita no modo texto
main ( ) {
FILE *fp;
fp = fopen (“teste.txt”, “w”)
}
Embora esteja correto, normalmente o código anterior é escrito da seguinte maneira:
main ( ) {
FILE *fp;
if ((fp = fopen (“teste.txt”, “w”) = = null) {
printf (“o arquivo não pode ser aberto \ n”);
return 0;
}
}
NULL é um valor nulo – inválido. Desta forma o programa irá saber se o arquivo foi
corretamente aberto, detectando qualquer tipo de erro que possa ocorrer. Para evitar erros,
sempre devemos conferir o sucesso da chamada de fopen.
Obs. Sempre que abrirmos um arquivo para escrita, se o arquivo existir o conteúdo do mesmo
será apagado.
Fechando um arquivo
todo arquivo aberto deve ser fechado quando não mais necessário, evitando perda de dados e
outros problemas.
Para tanto a função fclose ( ) deve ser usada. Seu protótipo é o seguinte:
Ponteiro do arquivo
int close ( FILE *fp )
fclose Retorna EOF se houver algum erro e 0 caso tenha sucesso..
1.3.3 Escrevendo e lendo em um arquivo texto
Escrevendo caracteres
Função: putc ( ou fputc ( ) ).
Protótipo:
int putc (int ch, FILE*fp);
Esta função pode ser usada para escrevermos caracteres em arquivos que foram previamente abertos por fopen
().
fp é um ponteiro de arquivo retornado por fopen.
ch é definido como int (2 bytes) mas apenas o byte menos significativo é levado em conta.
Se putc ( ) falha ela devolve o caracter de fim de arquivo EOF
Lendo caracteres
Função getc ( ) (ou fgetc())
Protótipo:
int getc (FILE,*fp);
Lê um caracter de um arquivo previamente aberto por fopen() usando o ponteiro fp. Da
mesma forma que putc, getc devolve um inteiro, mas apenas o byte menos significativo é
usado.
Quando chega ao final do arquivo getc ( ) devolve o caracter EOF.
Ex. No código seguinte um arquivo é lido até o seu final
do {
ch = getc (fp);
}while (ch!=EOF);
Quando ocorre um erro, getc também retorna EOF.
Lendo e escrevendo strings
Funções: fputs e fgets
As funções: fputs () e fgets() – efetuam operações de leitura e escrita de e para arquivos de
forma semelhante a getc e putc. Contudo, elas lêem e escrevem strings, ao invés de
caracteres.
Protótipos:
int fputs ( const char * str, FILE *fp);
char * fgets ( char *str, int lenght, FILE *fp);
fgets retorna um ponteiro para STR
fputs escreve a string str no arquivo especificado. Devolve EOF se um erro ocorre.
fgets lê uma string do arquivo especificado até que um caracter de nova linha seja
encontrado ou que length-1 caracteres tenham sido lidos, se o caracter de nova linha for lido
ele será inserido na string. A string resultante será terminada por um ‘\0’. Retorna NULL se
ocorre um erro ou fim de arquivo.
Exemplo1: Lê a primeira linha do arquivo “agenda.cpp”
int main(int argc, char **argv)
{ FILE *fp;
char buffer[100];
strcpy(buffer,"");
if ((fp=fopen("agenda.cpp","r"))==NULL) {
puts("erro ao abrir o arquivo");
return 0;
}
fgets(buffer,100,fp);
puts(buffer);
getch();
return 0;
}
1.3.3.1.1
Exemplo 2 – Escrevendo strings em um arquivo
int main(int argc, char **argv)
{ FILE *fp;
char buffer[100];
strcpy(buffer,"");
if ((fp=fopen("agenda.txt","a"))==NULL) {
puts("erro ao abrir o arquivo");
return 0;
}
do {
printf ("Digite uma string (enter para sair:\n");
gets(buffer);
// gets não guarda o ‘\n‘. temos que usar o strcat para
// colocar o ‘\n‘ no final da string se quisermos saltar uma linha no
// arquivo.
strcat (buffer,"\n");
// função de concatenação de strings
fputs (buffer, fp);
}while (*buffer!='\n');
fclose(fp);
}
exemplo 3 – Lendo um arquivo e apresentando na tela com fgets()
int main(int argc, char **argv)
{ FILE *fp;
char buffer[100];
strcpy(buffer,"");
if ((fp=fopen("agenda.cpp","r"))==NULL) {
puts("erro ao abrir o arquivo");
return 0;
}
fgets(buffer,100,fp);
while (fgets(buffer,100,fp)!=NULL) {
printf("%s",buffer);
}
fclose(fp);
getch();
}
Exercícios:
1. Construir um programa que leia uma string e escreva essa string em um arquivo
chamado ex1.txt caractere a caractere usando a função putc.
2. Construir um programa que leia o arquivo ex1.txt e guarde os caracteres lidos em uma
string. Posteriormente imprima a string na tela.
3. Construir um programa que leia palavras do teclado para um vetor de 20 strings.
Posteriormente, grave as strings guardadas no vetor no arquivo ex2.txt usando a função
fputs().
4. Construir um programa que leia o arquivo ex2.txt. usando a função fgets e apresente as
strings lidas na tela.
5. Criar um programa que permita a cópia de arquivos texto. O programa deve pedir o nome
do arquivo que se quer copiar e o nome do arquivo que será a cópia.
6. Criar um programa que conte o número de palavras de um arquiv. Dica: Uma palavra
pode ser terminada por um espaço (‘ ‘), um caractere de tabulação (‘\t’) ou um caractere
de nova linha (‘\n’).
7. Construir um programa que leia uma palavra do teclado e conte o número de ocorrências
da palavra lida em um arquivo texto.
8. Criar um programa que leia um arquivo e crie uma cópia “criptografada” do arquivo lido
somando-se o valor 1 a cada caracter lido no arquivo original. Criar um programa para
decriptografar o arquivo subtraindo 1 de cada caractere lido. Sugestão: Não criptografe o
caractere de nova linha (‘\n’).
9. Construa um programa que conte o número de ocorrências de uma seqüência de
caracteres dentro de um arquivo texto. A seqüência de caracteres deve ser lida do
teclado e não deve conter nem espaços, nem tabs, nem caracteres de nova linha. Neste
caso a seqüência pode estar dentro de um outra seqüência. Por exemplo, se procurarmos a
seqüência “ana” em um arquivo que contém o texto:
“Mariana é uma garota de Americana muito bacana”.
Neste caso, a seqüência ana ocorre três vezes:
Mariana é uma garota de Americana muito bacana.
1.4
Outras Funções de manipulação de arquivos
feof() – Determina a ocorrência de fim de arquivo quando este é aberto em modo binário.
Protótipo:
int feof(FILE *fp)
Comentário: No caso de arquivos binários, é possível que um dos dados lidos seja o valor
EOF. Portanto, não é confiável utilizar este caractere para determinar o fim de arquivo.
Assim, devemos usar a função feof() no lugar do teste de fim de arquivo.
rewind() – Reposiciona o indicador de posição do arquivo em seu início.
Protótipo:
void rewind (FILE * fp)
A medida que avançamos na leitura ou escrita de um arquivo, o seu indicador de posição
avança junto. Assim, se quisermos voltar ao início do arquivo sem fechá-lo, devemos usar a
função rewind().
ferror() – determina se uma operação com um arquivo produziu um erro.
Protótipo:
int ferror(FILE *fp);
ferror informa retornando verdadeiro se ocorreu um erro na última operação realizada no
arquivo. Ou seja, ferreor deve ser testada logo após as operações que se deseja verificar.
remove() - apaga um arquivo
Protótipo:
int remove(const char *nomearq)
Esta função apaga o arquivo indicado com pelo nome. Esta função deve ser usada
cuidadosamente. É sempre bom utilizar questionar o usuário se este deseja realmente apagar
o arquivo em questão.
1.5
Lendo e escrevendo em Arquivos Binários - fread() e fwrite()
Até agora, usamos o sistema de arquivos de C para lermos arquivos texto. No entanto, C
também permite que criemos arquivos do tipo binário. Um arquivo binário é uma forma de
organização que permite o armazenamento sequencial de estruturas maiores que um byte
(char).
Finalidade – a finaldade deste tipo de organização é podermos Ter em armazenamento
secundário uma forma de organização mais eficiente. Um aplicação imediata é no progama
da agenda. Neste caso, os dados são armazenados em um vetor de registros. Da mesma
forma, podemos armazenar os registros em um arquivo binário usando fread e fwrite.
1.5.1 Operador que determina o tamanho de tipos de dados - sizeof()
O operador sizeof será bastante utilizado na manipulação de arquivos binários. Isso porque,
para usarmos fread ou fwrite precisamos determinar o número de bytes que desejamos ler ou
gravar no arquivo binário. Como saber exatamente o tamanho de uma variável? Simples:
basta usar sizeof tomando como parâmetro o seu tipo.
Por exemplo, o programa abaixo imprime os tamanho em bytes dos tipos char, int, float,
double e long double:
int main()
int a,b,c,d,e;
a=sizeof(char);
b=sizeof(int);
c=sizeof(float);
d=sizeof(double);
e=sizeof(long double);
printf (" char: %d \n int: %d \n float: %d \n double: %d \n
long double: %d \n",a,b,c,d,e);
getch();
}
sizeof pode ser usado para determinar o tamanho de structs.
Exercício: Construa um struct para conter os dados de uma pessoa: Nome, telefone, endereco,
e-mail, data de nascimento e CPF. Use sizeof para imprimir o tamanho da struct que você
declarou.
1.5.2 Função fwrite()
Protótipo:
Size_t fwrite(const void *buffer, size_t numbytes, size_t count, FILE *fp);
Parâmetros:
- buffer – ponteiro para uma região da memória que contém os
dados que serão escritos no arquivo
- numbytes – determina o número de bytes de cada item
(elemento individual) a ser escrito no arquivo.
- Count determina o número de itens a serem escritos no
arquivo.
Os bytes serão pegos na memória a partir do endereço
especificado por buffer e escritos no arquivo.
- fp é um ponteiro para um arquivo previamente aberto por
fopen no modo binário.
1.5.3 Função fread()
Protótipo:
Size_t fread(void *buffer,size_t numbytes, size_t count, FILE *fp);
Parâmetros:
- buffer – ponteiro para uma região da memória que irá receber
os dados que serão lidos do arquivo
- numbytes – determina o número de bytes de cada item
(elemento individual) a ser lido do arquivo.
- Count determina o número de itens a serem lidos do arquivo.
Os bytes serão lidos do arquivo
e armazenados
na memória a
partir do endereço especificado por buffer.
- fp é um ponteiro para um arquivo previamente aberto por
fopen no modo binário.
1.5.4 Exemplo1 - Escrevendo número inteiros em um arquivo
// Programa que lê n números inteiros do teclado e armazena os números em
um arquivo binário
int main()
{
FILE *fp;
int numero,n,i;
char nomearq[15];
// Le o nome do arquivo
printf("Digite o nome do arquivo:");
scanf("%s",nomearq);
//abre o arquivo para escrita em modo binário
fp=fopen(nomearq,"wb");
if (fp==NULL){
puts("erro");
getch();
return 0;
}
printf("Digite o número de elementos
scanf("%d",&n);
a serem lidos:");
for(i=0;i<n;i++){
printf("Digite o elemento %d:",i+1);
scanf("%d",&numero); // Le um número
// escreve o numero no arquivo
fwrite(&numero,sizeof(int),1,fp);
}
if(fclose(fp)!=0)
printf("Erro ao fechar o arquivo");
}
1.5.5 Operador de endereço &
Neste exemplo n números inteiros são lidos do teclado para a memória na variável numero,
e posteriormente gravados no arquivo. Observe que na função fwrite o parâmetro buffer e
substituído por &numero. O operador & usado é chamado de operador de endereço. Ele é
usado porque, conforme visto anteriormente, buffer deve ser um ponteiro (endereço) para a
região da memória onde se encontra o dado a ser gravado por fwrite. Ou seja, a função do &
junto a uma variável é obter o endereço desta variável. Este é o mesmo motivo pelo qual
utilizamos o operador & nas variáveis a serem lidas por scanf.
1.5.6 Exemplo 2 – Programa que lê um arquivo binário de números inteiros e
imprime na tela
int main()
{
FILE *fp;
int numero,n,i;
char nomearq[15];
// Le o nome do arquivo
printf("Digite o nome do arquivo:");
scanf("%s",nomearq);
//abre o arquivo para leitura em modo binário
fp=fopen(nomearq,"rb");
if (fp==NULL){
puts("erro");
getch();
return 0;
}
fread(&numero,sizeof(int),1,fp); // tenta ler o primeiro elemento do
//arquivo
while(!feof(fp)){ // feof testa se não é fim de arquivo
printf("%d\n",numero); // imprime o número
fread(&numero,sizeof(int),1,fp);
// escreve o numero no arquivo
// le o próximo número
}
getch();
if(fclose(fp)!=0)
printf("Erro ao fechar o arquivo");
}
Neste exemplo, o procedimento de leitura do arquivo é semelhante ao de leitura de um
arquivo texto. Lê-se primeiro elemento fora do while. No entanto, ao invés do teste com o
caractere EOF, a função feof() deve ser utilizada.
1.5.7 Exemplo 3 - Escrevendo um vetor de inteiros em um arquivo binário
int main()
{
FILE *fp;
int i, n, vetint[100];
char nomearq[15];
// Le o nome do arquivo
printf("Digite o nome do arquivo:");
scanf("%s",nomearq);
//abre o arquivo para escrita em modo binário
fp=fopen(nomearq,"wb");
if (fp==NULL){
puts("erro");
getch();
return 0;
}
// le o numero de elementos a ser lido para o vetor
printf("Digite o número de elementos do vetor:");
scanf("%d",&n);
// le os números preenchendo o vetor
for (i=0;i<n;i++) {
printf("Digite o elemento %d",i+1);
scanf("%d",&vetint[i]);
}
// escreve o conteudo do vetor de uma única vez
fwrite(vetint,sizeof(int),n,fp);
//fecha o arquivo
if(fclose(fp)!=0)
printf("Erro ao fechar o arquivo");
}
Neste exemplo, o vetor foi previamente lido elemento a elemento do teclado. Posteriormente
o vetor foi escrito de uma única vez através de fwrite. Veja que o parâmetro count de fwrite
foi substituído por n que determina o número de elementos lidos para o vetor. Isso significa
que o número de bytes a ser gravado no arquivo nesse caso será igual a sizeof(int)*count a
partir do endereço vetint.
1.5.8 Nomes de vetores são ponteiros
Observe que o no último exemplo, o parâmetro buffer de fwrite foi substituído apenas por
vetint e não por &vetint. Isso ocorre porque na realidade, um nome de vetor em C já é um
endereço de memória. Ou seja, o nome de um vetor possui um endereço que aponta para a
região da memória onde se encontram as posições daquele vetor. Mais especificamente, um
nome de vetor possui o endereço (aponta) de memória da primeira posição do vetor. Por esse
motivo, o operador & é desnecessário quando usamos vetores.
Por esse mesmo motivo, na função scanf, quando vamos ler uma string (vetor de char), não
precisamos usar o operador de endereço.
Download

1 Arquivos em C