SOP-arquivos

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar

Arquivos na linguagem C

Para um programador, arquivos são repositórios permanentes de dados, os quais usualmente ficam em midia não volátil (disco rígido, CD ou DVD, pendrive, e outros). Arquivos servem para guardar informações que devem continuar a existir mesmo que termine o processo que as criou. Um exemplo é a agenda, cujo conteúdo deve ser preservado para futuras consultas e modificações. Mas além de serem depósitos de dados, arquivos possuem características bem definidas do ponto de vista de programação.

Um arquivo é uma sequência de bytes, que pode ser acessada, lida e modificada por meio de funções específicas existentes na biblioteca padrão da linguagem C. Essas funções servem para abrir e fechar um arquivo, ler e escrever uma certa quantidade de bytes, e mudar a posição da próxima leitura ou escrita.

Escrevendo em um arquivo

Abaixo segue um exemplo de um programa para escrever uma linha em um arquivo:

#include<stdio.h>

int main() {
  char linha[256];
  FILE * arquivo;

  printf("Digite uma linha: ");
  scanf("%[^\n]", linha);
  
  printf("Vou gravar isto no arquivo teste.txt\n");
  arquivo = fopen("teste.txt", "w");
  if (arquivo == NULL) {
    perror("Nao conseguiu abrir o arquivo");
    return 1;
  }
  fprintf(arquivo, "%s\n", linha);
  fclose(arquivo);

  printf("Pronto ... veja o arquivo teste.txt !\n");
}

Esse pequeno programa evidencia que trabalhar com arquivos é muito parecido a trabalhar com escrita na tela e leitura do teclado. Mas primeiro deve-se focar nas linhas do programa que têm relação direta com arquivos. Para começar, um arquivo é referenciado por uma variável do tipo FILE *:

  FILE * arquivo;

Em seguida, o arquivo a ser lido ou escrito deve ser aberto, usando-se a função FILE * fopen(char * nome_arquivo, char * modo):

  arquivo = fopen("teste.txt", "w");

Como se pode ver, a função fopen precisa de dois parâmetros do tipo char * (quer dizer, dois parâmetros string). O primeiro é o caminho do arquivo a ser aberto, e o segundo é o modo de abertura (o que se pretende fazer com ele: ler, escrever, adicionar dados ao final, ler e escrever). No exemplo acima, vai-se abrir o arquivo teste.txt para escrita (modo "w", de write). O valor de retorno de fopen é do tipo FILE *, e corresponde a uma descrição do arquivo que foi aberto. Caso aconteça algum erro (ex: não há permissão para criar o arquivo), fopen retorna o valor NULL. O exemplo testa se isto acontece da seguinte forma:

  if (arquivo == NULL) {
    perror("Nao conseguiu abrir o arquivo");
    return 1;
  }

Uma vez o arquivo estando aberto para escrita, pode-se escrever nele usando-se a função fprintf. Essa função é idêntica à função printf que se usa para escrever na tela. Porém fprintf precisa de um parâmetro adicional, deve ser a variável que corresponde ao arquivo aberto (primeiro parâmetro da função):

  fprintf(arquivo, "%s\n", linha);

Finalmente, quando não se quer mais escrever no arquivo deve-se chamar a função fclose para fechá-lo:

  fclose(arquivo);

Lendo uma linha de um arquivo

A leitura de um arquivo é parecida, com algumas pequenas modificações em sua abertura (deve-se indicar que o mode de operação será "r", de "read"), e usando-se a função fscanf para ler dados do arquivo. O exemplo abaixo mostra um programa para ler a primeira linha de um arquivo:

#include<stdio.h>

int main() {
  char linha[1024];
  char nome[256];
  FILE * arquivo;

  printf("Digite o nome de um arquivo: ");
  scanf("%s", nome);
  
  printf("Vou mostrar a primeira linha do arquivo %s\n", nome);

  arquivo = fopen(nome, "r");
  if (arquivo == NULL) {
    perror("Ops ... erro ao acessar esse arquivo: ");
    return 1;
  }

  fgets(linha, 1024, arquivo);
  fclose(arquivo);

  printf("Eis a primeira linha do arquivo: \n\n%s\n\n", linha);

}

Como adiantado, na abertura com fopen deve-se usar o valor "r" para o modo:

  arquivo = fopen(nome, "r");

Já a leitura de uma linha pode ser feita mais facilmente com a função fgets:

  fgets(linha, 1024, arquivo);

A função char * fgets(char * resultado, int max_caracteres, FILE * arquivo) lê uma linha de um arquivo com até max_caracteres e a guarda na variável passada no parâmetro resultado. No exemplo acima, fgets lê uma linha com até 1024 caracteres e a guarda na variável linha.

Lendo todas as linhas de um arquivo

Nesse próximo exemplo, abre-se um arquivo para leitura e lêem-se todas suas linhas, mostrando-as na tela:

#include<stdio.h>

int main() {
  char linha[1024];
  char nome[256];
  char * ok;
  FILE * arquivo;

  printf("Digite o nome de um arquivo: ");
  scanf("%s", nome);
  
  printf("\nVou mostrar todas as linhas do arquivo %s\n\n", nome);

  arquivo = fopen(nome, "r");
  if (arquivo == NULL) {
    perror("Ops ... erro ao acessar esse arquivo: ");
    return 1;
  }

  do {
    ok = fgets(linha, 1024, arquivo);
    if (ok != NULL) printf("%s", linha);
  } while (! feof(arquivo));

  fclose(arquivo);

}

A diferença em relação ao exemplo anterior está na sequência que lê as linhas do arquivo:

 do {
    ok = fgets(linha, 1024, arquivo); // Lê a próxima linha do arquivo
    if (ok != NULL) printf("%s", linha); // se conseguiu ler algo, mostra na tela
  } while (! feof(arquivo)); // para se chegou ao fim de arquivo

Há algumas novidades aqui:

  1. O valor de retorno de fgets é testado quanto a sua validade: a função fgets retorna o valor NULL se não conseguiu ler algo do arquivo. Isto pode ter ocorrido por um erro de leitura (ex: disco com defeito) ou porque se chegou ao fim do arquivo, e assim nada mais existe para ser lido. Esse teste aparece na linha:
    if (ok != NULL) printf("%s", linha)
    
    .
  2. A leitura continua enquanto não se chegar ao fim de arquivo: o teste de fim de arquivo se faz com a função int feof(FILE * arquivo), que retorna 0 se ainda não se chegou ao final do arquivo, ou 1 caso contrário. A chamada de feof se faz ao final do laço:
      } while (! feof(arquivo));
    

Escrevendo e lendo valores quaisqer

Como se viu, a escrita em arquivos pode ser feita com a função fprintf, que é similar a printf. Isto possibilita a escrita de qualquer tipo de valor em um arquivo, como se pode ver no próximo exemplo, em que se escreve uma contagem de 0 a 9 (um número por linha):

#include<stdio.h>

int main() {
  char nome[256];
  FILE * arquivo;
  int n;

  printf("Digite o nome de um arquivo: ");
  scanf("%s", nome);
  
  printf("\nVou gravar uma contagem numérica nesse arquivo\n");

  arquivo = fopen(nome, "w");
  if (arquivo == NULL) {
    perror("Ops ... erro ao acessar esse arquivo: ");
    return 1;
  }

  n = 0;
  while (n < 10) {
    fprintf(arquivo, "%d\n", n);
    n ++;
  }

  fclose(arquivo);

}

A contagem é gerada e escrita no arquivo nesta parte do programa:

  n = 0;
  while (n < 10) {
    fprintf(arquivo, "%d\n", n);
    n ++;
  }

Isto é quase o mesmo que escrever a contagem na tela usando-se printf. E fprintf realmente funciona exatamente da mesma forma que printf.

De forma análoga, a leitura de um arquivo que contém números (um por linha) pode ser feita com o programa abaixo:

#include<stdio.h>

int main() {
  char nome[256];
  FILE * arquivo;
  int n;
  int ok;

  printf("Digite o nome de um arquivo: ");
  scanf("%s", nome);
  
  printf("\nVou ler uma contagem numérica desse arquivo\n");

  arquivo = fopen(nome, "r");
  if (arquivo == NULL) {
    perror("Ops ... erro ao acessar esse arquivo: ");
    return 1;
  }

  do {
    ok = fscanf(arquivo, "%d", &n);
    if (ok > 0) printf("Leu: %d\n", n);
  } while (! feof(arquivo));

  fclose(arquivo);

}

A parte desse programa que lê os números contidos no arquivo é:

  do {
    ok = fscanf(arquivo, "%d", &n);
    if (ok > 0) printf("Leu: %d\n", n);
  } while (! feof(arquivo));

Pode-se ver que assim como existe fprintf, há a função fscanf, que funciona da mesma maneira que scanf. O programa desse exemplo funciona para qualquer arquivo que contenha números. Pode haver mais de um número por linha, contanto que estejam separados por espaços (não pode haver nada diferente de números e espaços). Finalmente, pode existir qualquer quantidade de linhas (experimente executar o programa !).

Lendo e escrevendo valores struct

Ler e gravar valores do tipo struct pode ser feito facilmente com as funções fprintf e fscanf. O exemplo abaixo lê do teclado valores para preencher os campos de uma variável do tipo struct Pessoa, em seguida a grava em um arquivo:

#include<stdio.h>

struct Pessoa {
  char nome[1024];
  int idade;
  char cpf[12];
};

int main() {
  char path[256];
  FILE * arquivo;
  char opcao;
  struct Pessoa umaPessoa;

  printf("Digite o nome de um arquivo: ");
  scanf("%s", path);
  
  printf("\nVou gravar registros de pessoas nesse arquivo\n");

  arquivo = fopen(path, "w");
  if (arquivo == NULL) {
    perror("Ops ... erro ao acessar esse arquivo: ");
    return 1;
  }

  do {
    printf("Nome: ");
    scanf(" %[^\n]", umaPessoa.nome);
    printf("CPF (sem pontos ou traços): ");
    scanf("%s", umaPessoa.cpf);
    printf("Idade: ");
    scanf("%d", &umaPessoa.idade);

    fprintf(arquivo, "%s:%s:%d\n", umaPessoa.nome, umaPessoa.cpf, umaPessoa.idade);

    printf("Continuar (S/N) ?: ");
    scanf(" %c", &opcao);
  } while ((opcao == 'S') || (opcao == 's'));

  fclose(arquivo);

}

A escrita dos valores da variável umaPessoa no arquivo foi realizada com um simples fprintf:

    fprintf(arquivo, "%s:%s:%d\n", umaPessoa.nome, umaPessoa.cpf, umaPessoa.idade);

O programador escolheu gravar os três valores na mesma linha do arquivo, porém separando-os com o caractere ":". Isso foi uma decisão arbitrária, por gosto do programador e por ele pensar que tornará mais fácil ler o arquivo futuramente. Note também que esse caractere ":" não pode aparecer nos valores de struct Pessoa, do contrário se confundiria com os dados gravados. O resultado do uso desse programa poderia ser:

Marcelo M. Sobral:33344455589:38
Luis B. Sobral:99900011198:10
James T. Kirk:77766677788:32

A leitura de um arquivo contendo dados no formato acima descrito pode ser feita com o programa abaixo:

#include<stdio.h>

struct Pessoa {
  char nome[1024];
  int idade;
  char cpf[12];
};

int main() {
  char path[256];
  FILE * arquivo;
  int ok;
  struct Pessoa umaPessoa;

  printf("Digite o nome do arquivo de pessoas: ");
  scanf("%s", path);
  
  printf("\nVou ler registros de pessoas desse arquivo\n\n");

  arquivo = fopen(path, "r");
  if (arquivo == NULL) {
    perror("Ops ... erro ao acessar esse arquivo: ");
    return 1;
  }

  do {
    ok = fscanf(arquivo, " %[^:]:%[^:]:%d", umaPessoa.nome, umaPessoa.cpf, &umaPessoa.idade);
    if (ok == 3) { // foram lidos três valores ?
      printf("Nome: %s\n", umaPessoa.nome);
      printf("CPF: %s\n", umaPessoa.cpf);
      printf("Idade: %d\n\n", umaPessoa.idade);
    }
  } while (! feof(arquivo));

  fclose(arquivo);

}

Veja que esse programa basicamente lê linhas de um arquivo até chegar ao seu fim. A leitura de cada linha se faz da seguinte forma:

    ok = fscanf(arquivo, " %[^:]:%[^:]:%d", umaPessoa.nome, umaPessoa.cpf, &umaPessoa.idade);

Veja que o fscanf lê três valores separados por ":" (talvez o compĺicado esteja no formato usado pelo fscanf para obter esse efeito ;-), e os coloca nos campos da variável umaPessoa. O formato do fscanf contém especificações para os três valores, sendo que "%[^:]" significa "leia tudo que não seja um caractere :" (o que tem o efeito de ler tudo até encontrar um :).

Uma alternativa para gravar os valores em um arquivo é escrever um valor por linha. Assim, o conteúdo do arquivo ficaria da seguinte forma:

Marcelo M. Sobral
33344455589
38
Luis B. Sobral
99900011198
10
James T. Kirk
77766677788
32

O programa que gera um arquivo nesse formato seria parecido com o programa para o formato anterior (valores na mesma linha e separados por ":"), tendo a seguinte modificação:

  do {
    printf("Nome: ");
    scanf(" %[^\n]", umaPessoa.nome);
    printf("CPF (sem pontos ou traços): ");
    scanf("%s", umaPessoa.cpf);
    printf("Idade: ");
    scanf("%d", &umaPessoa.idade);

    // Escrita dos três valores, mas um por linha
    fprintf(arquivo, "%s\n%s\n%d\n", umaPessoa.nome, umaPessoa.cpf, umaPessoa.idade);

    printf("Continuar (S/N) ?: ");
    scanf(" %c", &opcao);
  } while ((opcao == 'S') || (opcao == 's'));

Para fazer a leitura desse arquivo, o programa também seria parecido com o anterior, mas com a seguinte modificação:

  do {
    ok = fscanf(arquivo, " %[^\n]", umaPessoa.nome);
    ok = ok + fscanf(arquivo, " %[^\n]", umaPessoa.cpf);
    ok = ok + fscanf(arquivo, "%d", &umaPessoa.idade);
    if (ok == 3) {
      printf("Nome: %s\n", umaPessoa.nome);
      printf("CPF: %s\n", umaPessoa.cpf);
      printf("Idade: %d\n\n", umaPessoa.idade);
    }
  } while (! feof(arquivo));