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>
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:
\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.As operações sobre arquivos em C podem ser feitas de duas formas:
FILE*
. 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.
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.
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.#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> int fcloseall (void)
Fecha todos os streams abertos, de forma similar a fclose
. Isso também é efetuado quando a função main
termina ou quando a função exit
é invocada.
#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:
#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:
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:
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, ^[ |
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"
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.
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.
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
.
snprintf
e asprintf
são mais seguras e evitam esse problema.
#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
.
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
.
Todos os arquivos contêm sequências de bytes, mas costuma-se dizer que um arquivo é “binário” quando seu conteúdo não é uma informação textual (ou seja, não representa um texto usando codificações como ASCII e outras).
Arquivos binários são usados para armazenar informações mais complexa que texto puro, como imagens, música, filmes, código executável, etc. A interpretação do conteúdo de um arquivo binário é de responsabilidade da aplicação envolvida.
A linguagem C oferece funções para ler e escrever blocos de bytes em arquivos, que efetuam a cópia desses bytes da memória da aplicação para o arquivo, ou vice-versa.
A funções a seguir permitem ler/escrever blocos de bytes em arquivos binários:
#include <stdio.h> size_t fread (void* data, size_t size, size_t count, FILE* stream)
Lê até count
blocos de tamanho size
bytes cada um e os deposita no vetor data
, a partir do stream indicado. Retorna o número de blocos lidos.
#include <stdio.h> size_t fwrite (const void* data, size_t size, size_t count, FILE* stream)
Escreve até count
blocos de tamanho size
bytes do vetor data
no stream indicado. Retorna o número de blocos escritos.
Este exemplo manipula um arquivo binário numeros.dat
contendo números reais. São implementadas (em arquivos separados) as operações de escrita de números no arquivo, listagem e ordenação do conteúdo:
#include <stdio.h> #include <stdlib.h> #include <time.h> #define ARQUIVO "numeros.dat" #define NUMVAL 10 int main (int argc, char *argv[]) { FILE* arq ; int i, ret ; float value[NUMVAL] ; // abre o arquivo em modo "append" arq = fopen (ARQUIVO, "a") ; if (!arq) { perror ("Erro ao abrir arquivo") ; exit (1) ; } // inicia gerador de aleatórios srand (clock()) ; // gera NUMVAL valores aleatórios reais for (i = 0; i< NUMVAL; i++) value[i] = random() / 100000.0 ; // escreve os valores gerados no final do arquivo ret = fwrite (value, sizeof(float), NUMVAL, arq) ; if (ret) printf ("Gravou %d valores com sucesso!\n", NUMVAL) ; else printf ("Erro ao gravar...\n") ; // fecha o arquivo fclose (arq) ; return (0) ; }
#include <stdio.h> #include <stdlib.h> #define ARQUIVO "numeros.dat" int main (int argc, char *argv[]) { FILE* arq ; float value ; // abre o arquivo em modo leitura arq = fopen (ARQUIVO, "r") ; if (!arq) { perror ("Erro ao abrir arquivo") ; exit (1) ; } // lê e imprime os valores contidos no arquivo fread (&value, sizeof(float), 1, arq) ; while (! feof(arq)) { printf ("%f\n", value) ; fread (&value, sizeof(float), 1, arq) ; } // fecha o arquivo fclose (arq) ; return (0) ; }
#include <stdio.h> #include <stdlib.h> #define ARQUIVO "numeros.dat" #define MAXVAL 100000 float value[MAXVAL] ; int num_values ; int main (int argc, char *argv[]) { FILE* arq ; int i, j, menor ; float aux ; // abre o arquivo em leitura/escrita, preservando o conteúdo arq = fopen (ARQUIVO, "r+") ; if (!arq) { perror ("Erro ao abrir arquivo") ; exit (1) ; } // lê números do arquivo no vetor num_values = fread (value, sizeof(float), MAXVAL, arq) ; printf ("Encontrei %d números no arquivo\n", num_values) ; // ordena os números (por seleção) for (i=0; i < num_values-1; i++) { // encontra o menor elemento no restante do vetor menor = i ; for (j=i+1; j < num_values; j++) if (value[j] < value[menor]) menor = j ; // se existe menor != i, os troca entre si if (menor != i) { aux = value[i] ; value[i] = value[menor] ; value[menor] = aux ; } } // retorna o ponteiro ao início do arquivo rewind (arq) ; // escreve números do vetor no arquivo fwrite (value, sizeof(float), num_values, arq) ; // fecha o arquivo fclose (arq) ; return (0) ; }
Para cada arquivo aberto em uma aplicação, o sistema operacional mantém um contador interno indicando a posição da próxima operação de leitura ou escrita. Esse contador é conhecido como ponteiro de posição (embora não seja realmente um ponteiro).
Por default, as operações em um arquivo em C ocorrem em posições sucessivas dentro do arquivo: cada leitura (ou escrita) corre após a leitura (ou escrita) precedente, até atingir o final do arquivo. Essa forma de acesso ao arquivo é chamada de acesso sequencial.
Por vezes uma aplicação precisa ler ou escrever em posições específicas de um arquivo, ou precisa voltar a ler uma posição do arquivo que já percorreu anteriormente. Isso ocorre frequentemente em aplicações que manipulam arquivos muito grandes, como vídeos ou bases de dados. Para isso é necessária uma forma de acesso direto a posições específicas do arquivo.
Em C, o acesso direto a posições específicas de um arquivo é feita através de funções de posicionamento de ponteiro, que permitem alterar o valor do ponteiro de posição do arquivo, antes da operação de leitura/escrita desejada.
As funções mais usuais para acessar o ponteiro de posição de um arquivo em C são:
#include <stdio.h> int fseek (FILE* stream, long int offset, int whence)
Ajusta posição do ponteiro no stream indicado.
O ajuste é definido por offset
, enquanto o valor de whence
indica se o ajuste é relativo ao início do arquivo (SEEK_SET
), à posição corrente (SEEK_CUR
) ou ao final do arquivo (SEEK_END
). Ver também fseeko
e fseeko64
. Exemplos:
// posiciona o ponteiro de "arq": fseek (arq, 1000, SEEK_SET) ; // 1000 bytes após o início fseek (arq, 300, SEEK_END) ; // 300 bytes antes do fim fseek (arq, -500, SEEK_CUR) ; // 500 bytes antes da posição atual
#include <stdio.h> void rewind (FILE* stream)
Reposiciona o ponteiro no início (posição 0) do stream indicado.
#include <stdio.h> long int ftell (FILE* stream)
Informa a posição corrente de leitura/escrita em stream
(ver também ftello
e ftello64
no manual).
minusc.txt
e escrever um arquivo maiusc.txt
contendo o mesmo texto em maiúsculas.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.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
).long
aleatórios, armazenados em modo binário;Mais exercícios no capítulo 11 da apostila do NCE/UFRJ.