Ferramentas do usuário

Ferramentas do site


prog2:acesso_a_arquivos

Acesso a arquivos

Aqui serão descritas algumas das funções mais usuais para operações de entrada/saída em arquivos na linguagem C. A maioria das funções aqui descritas está declarada no arquivo de cabeçalho stdio.h:

#include <stdio.h>

Conceitos básicos

Tipos de arquivos

Um arquivo armazena uma sequência de bytes, cuja interpretação fica a cargo da aplicação. Contudo, para facilitar a manipulação de arquivos, a linguagem C considera dois tipos de arquivos:

  • arquivos de texto: contém sequências de bytes representando caracteres de texto, separadas por caracteres de controle como \n ou \r e usando uma codificação como ASCII, ISO-8859-1 ou Unicode. São usados para armazenar informação textual, como código-fonte, páginas Web, arquivos de configuração, etc.
  • arquivos binários: contém sequências de bytes, cuja interpretação fica totalmente a cargo da aplicação. São usualmente empregados para armazenar imagens, vídeos, músicas, dados compactados, etc.

Streams e descritores

As operações sobre arquivos em C podem ser feitas de duas formas:

  • por streams: acesso em um nível mais elevado, independente de sistema operacional e portanto portável. Permite entrada/saída formatada, mas pode ter um desempenho inferior ao acesso de baixo nível. Streams são acessadas através de variáveis do tipo FILE*.
  • por descritores: acesso através dos descritores de arquivo fornecidos pelo sistema operacional, pouco portável mas com melhor desempenho.

Na sequência deste texto serão apresentadas as funções de acesso usando streams, que valem para qualquer sistema operacional. Explicações sobre entrada/saída usando descritores UNIX podem ser encontradas nesta página.

Entradas e saídas padrão

Cada programa em execução tem acesso a três arquivos padrão definidos no arquivo de cabeçalho stdio.h, que são:

  • FILE* stdin: a entrada padrão, normalmente associada ao teclado do terminal, usada para informar dados ao programa (scanf, por exemplo).
  • FILE* stdout: a saída padrão, normalmente associada à tela do terminal, usada para as saídas normais do programa (printf, por exemplo).
  • FILE* stderr: a saída de erro, normalmente associada à tela do terminal, usada para mensagens de erro.

Abrindo e fechando arquivos

Antes de ser usado, um arquivo precisa ser “aberto” pela aplicação (com execção dos arquivos padrão descritos acima, que são abertos automaticamente). Isso é realizado através da chamada fopen:

#include <stdio.h>
FILE* fopen (const char *filename, const char *opentype)

Abre um arquivo indicado por filename e retorna um ponteiro para o stream. A string opentype define o modo de abertura do arquivo:

  • r : abre um arquivo existente para leitura (read).
  • w : abre um arquivo para escrita (write). Se o arquivo já existe, seu conteúdo é descartado. Senão, um novo arquivo vazio é criado.
  • a : abre um arquivo para concatenação (append). Se o arquivo já existe, seu conteúdo é preservado e as escritas serão concatenadas no final do arquivo. Senão, um novo arquivo vazio é criado.
  • r+ : abre um arquivo existente para leitura e escrita. O conteúdo anterior do arquivo é preservado e o ponteiro é posicionado no início do arquivo.
  • w+ : abre um arquivo para leitura e escrita. Se o arquivo já existe, seu conteúdo é descartado. Senão, um novo arquivo vazio é criado.
  • a+ : abre um arquivo para escrita e concatenação. Se o arquivo já existe, seu conteúdo é preservado e as escritas serão concatenadas no final do arquivo. Senão, um novo arquivo vazio é criado. O ponteiro de leitura é posicionado no início do arquivo, enquanto as escritas são efetuadas no seu final.

Os modos a e a+ sempre escreverão no final do arquivo, mesmo se o cursor do mesmo for reposicionado.

#include <stdio.h>
int fclose (FILE* stream)

Fecha um stream. Dados de saída em buffer são escritos, enquanto dados de entrada são descartados.

#include <stdio.h>
FILE* freopen (const char *filename, const char *opentype, FILE *stream)

Fecha e abre novamente um stream, permitindo alterar o arquivo e/ou modo de abertura.

Exemplo: abrindo o arquivo x em leitura:

fopen-read.c
#include <stdio.h>
#include <stdlib.h>
 
int main ()
{
   FILE* arq ;
 
   arq = fopen ("x", "r") ;
 
   if ( ! arq )
   {
      perror ("Erro ao abrir arquivo x") ;
      exit (1) ; // encerra o programa com status 1
   }
 
   fclose (arq) ;
   exit (0) ; 
}

Exemplo: abre o arquivo x em leitura/escrita, criando-o se não existir:

fopen-write.c
#include <stdio.h>
 
int main (int argc, char *argv[])
{
   FILE* arq ;
 
   arq = fopen ("x", "w+") ;
 
   if ( ! arq )
   {
      perror ("Erro ao abrir/criar arquivo x") ;
      exit (1) ; // encerra o programa com status 1
   }
 
   fclose (arq) ;
   exit (0) ;
}

Arquivos-texto

Arquivos-texto contêm sequências de bytes representando um texto simples (sem formatações especiais, como negrito, itálico, etc), como código-fonte ou uma página em HTML, por exemplo.

Em um arquivo-texto, os caracteres do texto são representados usando uma codificação padronizada, para que possam ser abertos por diferentes aplicações em vários sistemas operacionais. As codificações de caracteres mais usuais hoje são:

  • ASCII: criada em 1963 para representar os caracteres comuns da língua inglesa, usando 7 bits (valores entre 0 e 127).
  • ISO-8859: conjunto de extensões da codificação ASCII para suportar outras línguas com alfabeto latino, como o Português. Os caracteres entre 0 e 127 os mesmos da tabela ASCII, enquanto os caracteres entre 128 e 255 são específicos. Por exemplo, a extensão ISO-8859-1 contém os caracteres acentuados e cedilhas da maior parte das linguagens ocidentais (Português, Espanhol, Francês etc).
  • UTF-8: codificação baseada no padrão Unicode, capaz de representar mais de um milhão de caracteres em todas as línguas conhecidas, além de sinais gráficos (como emojis). Os caracteres em UTF-8 podem usar entre 1 e 4 bytes para sua representação, o que torna sua manipulação mais complexa em programas.

Além dos caracteres em si, as codificações geralmente suportam caracteres de controle, que permitem representar algumas estruturas básicas de formatação do texto, como quebras de linha. Alguns caracteres de controle presentes nas codificações acima são:

nome valor representação
null 0 NUL, \0, ^@
bell 7 BEL, \a, ^G
backspace 8 BS, \b, ^H
tab 9 HT, \t, ^I
line feed 10 LF, \n, ^J
form feed 12 FF, \f, ^L
carriage return 13 CR, \r, ^M
escape 27 ESC, ^[

Saída simples

Estas funções permitem gravar caracteres ou strings simples em streams.

#include <stdio.h>
 
int fputc   (int c, FILE* stream)   // escreve o caractere equivalente a "c" em "arq"
int putc    (int c, FILE* stream)   // idem, implementada como macro
int putchar (int c)                 // idem, em "stdout"
 
int fputs (const char *s, FILE* stream)  // escreve a string "s" no stream indicado
int puts  (const char *s)                // idem, em "stdout"

Entrada simples

Estas funções permitem ler caracteres isolados de um stream. O valor lido é um int indicando o caractere lido ou então o valor especial EOF (End-Of-File):

#include <stdio.h>
 
int fgetc (FILE* stream)  // Lê o próximo caractere do //stream// indicado
int getc  (FILE* stream)  // Idem, implementado como macro (mais rápido)
int getchar ()            // Idem, sobre ''stdin''

Para a leitura de strings:

#include <stdio.h>
char* gets (char *s)

Lê caracteres de stdin até encontrar um newline e os armazena na string s. O caractere newline é descartado.

Atenção: a função gets é perigosa, pois não provê segurança contra overflow na string s. Sempre que possível, deve ser usada a função fgets ou getline.

#include <stdio.h>
char* fgets (char *s, int count, FILE *stream)

Lê uma linha de caracteres do stream e a deposita na string s. O tamanho da linha é limitado em count-1 caracteres, aos quais é adicionado o '\0' que marca o fim da string. O newline é incluso.

Saída formatada

As operações de entrada e saída formatada usam padrões para formatação dos diversos tipos de dados descritos em livros de programação em C e no manual da GLibC.

#include <stdio.h>
int printf (const char* format, ...)

Escreve dados usando a formatação definida em format no stream de saída padrão stdout.

#include <stdio.h>
int fprintf (FILE* stream, const char* format, ...)

Idêntico a printf, usando o stream indicado.

#include <stdio.h>
int sprintf (char* s, const char* template, ...)

Similar a printf, mas a saída é depositada na string s.

Atenção: o programador deve garantir que s tenha tamanho suficiente para receber a saída; caso contrário, pode ocorrer um overflow com consequências imprevisíveis. As funções snprintf e asprintf são mais seguras e evitam esse problema.

Entrada formatada

#include <stdio.h>
int scanf (const char* format, ...)

Lê dados do stream stdin de acordo com a formatação definida na string format. Os demais argumentos são ponteiros para as variáveis onde os dados lidos são depositados. Retorna o número de dados lidos ou EOF.

#include <stdio.h>
int fscanf (FILE* stream, const char* format, ...)

Similar a scanf, mas usando como entrada o stream indicado.

#include <stdio.h>
int sscanf (const char* s, const char* format, ...)

Similar a scanf, mas usando como entrada a string s.

Erros e fim de stream

int EOF

Esta macro define o valor retornado por algumas funções para indicar fim do arquivo ou erro.

#include <stdio.h>
int feof (FILE* stream)

Retorna valor não nulo se o stream chegou ao seu fim.

#include <stdio.h>
int ferror (FILE* stream)

Retorna um valor não nulo se ocorreu um erro em algum acesso anterior ao stream.

Além de ajustar o indicador de erro do stream, as funções de acesso a streams também ajustam a variável errno.

Exercícios

  1. Escreva um programa em C para informar o número de caracteres presentes em um arquivo de texto.
  2. Escreva um programa em C que leia um arquivo com números reais (um número por linha) e informe a média dos valores lidos.
  3. Escreva um programa em C para ler um arquivo minusc.txt e escrever um arquivo maiusc.txt contendo o mesmo texto em maiúsculas.
  4. O arquivo mapa.txt contém o mapa de um nível do jogo Boulder Dash. Escreva um programa em C que carregue esse mapa em uma matriz de caracteres.
  5. Escreva um programa mycp para fazer a cópia de um arquivo em outro: mycp arq1 arq2. Antes da cópia, arq1 deve existir e arq2 não deve existir. Mensagens de erro devem ser geradas caso essas condições não sejam atendidas ou o nome dado a arq2 seja inválido. Para acessar os nomes dos arquivos na linha de comando use os parâmetros argc e argv (veja aqui).
  6. o comando grep do UNIX imprime na saída padrão (stdout) as linhas de um arquivo de entrada que contenham uma determinada string informada como parâmetro. Escreva esse programa em C (dica: use a função strstr).

Mais exercícios no capítulo 11 da apostila do NCE/UFRJ.

prog2/acesso_a_arquivos.txt · Última modificação: 2020/03/10 16:57 por maziero