Universidade Federal de Minas Gerais 
Instituto de Ciências Exatas 
Departamento de Ciência da Computação

Algoritmos e Estruturas de Dados I

Entrada e Saída

Os programas podem interagir com o ambiente através de operações de entrada e saída. As operações de entrada e saída podem ser feitas de várias formas, por exemplo utilizando arquivos ou utilizando soquetes de redes, aqui iremos enfatizar o uso de arquivos.

A organização da Entrada e Saída no ambiente C dá preferência à simplicidade. Não existem comandos especiais na linguagem C para lidar com entrada e saída, o que existe é um conjunto de funções padronizadas. Estas funções devem ser implementadas de forma padronizada nos diferentes sistemas onde a linguagem C é disponibilizada. Além destas funções padronizadas (que são bastante simples e não dão suporte nem mesmo ao conceito de diretório!) o programador que utiliza a linguagem C eventualmente faz uso da API (Application  Program Interface) provida pelo sistema operacional. A maior parte do material relacionado às funções simples fica definido no arquivo de cabeçalho denominado stdio.h, mas os projetistas da linguagem C tentaram definir um certo padrão na chamada de funções providas pelo sistema operacional através dos cabeçalhos syscalls.h, fcntl.h, sys/types.h, sys/stat.h, dirent.h dentre outras opções.

A linguagem C implementa vários grupos de funções de entrada e saída: (i) um grupo de funções de entrada e saída faz uso da noção de “texto” (text input/output), (ii) um grupo de funções de entrada e saída faz uso da noção de dados binários e (iii) um grupo de funções de entrada e saída dá apoio a obtenção e ajustamento da posição de leitura e escrita. Um fluxo de texto consiste de uma sequência de linhas; cada linha termina com o caractere mítico “nova linha” (new line) (se o sistema onde executa o ambiente C não tiver este conceito a implementação das funções padrão C vão fazer o que for necessário para parecer que o sistema atende a estes requisitos de caracteres, linhas etc). Por exemplo o caractere mítico “nova linha” pode ser representado por dois caracteres: o “retorno de carro” (carriage return) e o caractere “avança linha” (linefeed). Abaixo são discutidos os conceitos relacionados à entrada e saída de dados binários e o grupo de funções de posicionamento (p.ex. fseek() e ftell()).

Sistemas de arquivo, diretório, arquivo, caminhos

Um ambiente de execução de programas em C não define um Sistema de Arquivos próprio. Operações com arquivos são feitas através das funções que veremos a seguir e que proveem a adaptação necessária aos vários sistemas (p.ex. Linux, Windows, iOS etc). É possível escrever programas que sejam independentes de plataforma, mas também é possível escrever programas que utilizam características específicas de um sistema (p.ex. Solaris, Linux ou Windows). 

Um sistema de arquivo consiste, em geral, de uma estrutura com elementos ou nodos de dois tipos: (1) diretórios ou pastas  e (2) arquivos. Os diretórios podem se associar entre si e com os arquivos através da relação "está diretamente contido em". O elemento y está diretamente contido no diretório x, significa que y é um arquivo ou um diretório e que y está no espaço de nomes definido diretamente no diretório x. Existe um diretório especial chamado diretório raiz que não está diretamente contido em nenhum outro diretório. Um sistema de arquivos vazio contém apenas o diretório raiz. O diretório raiz contém direta ou indiretamente todos os diretórios e arquivos de um sistema de arquivos. Os diretórios, exceto a raiz, podem ser chamados de subdiretórios. Esta relação forma uma estrutura hierárquica ou em forma de "árvore". As hierarquias são representadas ou desenhadas em geral na forma de uma árvore invertida, com o elemento raiz na parte de cima (ao invés de ficar na parte de baixo!). As "folhas" desta árvore podem ser diretórios ou arquivos. Esta estrutura hierárquica do sistema de arquivos permite ainda referências genealógicas, p. ex. o nodo raiz é o diretório ancestral de todos os arquivos, o diretório x é filho do diretório raiz, o diretório raiz é o pai do diretório x. 

Cada diretório corresponde a um espaço de nomes (name space). Com isto é possível a existência de arquivos com o mesmo nome, mas em diretórios diferentes. A identificação de um arquivo envolve uma sequência de nomes. Uma sequência de nomes é denominada caminho (path), e a separação dos nomes depende de cada sistema.  Um trecho de caminho x-y é válido se y está diretamente contido no diretório x. Um caminho é valido se todos os trechos são válidos. Na plataforma Windows existe um sistema de arquivo para cada unidade de disco. Nas plataformas derivadas do Sistema Unix, p.ex. Linux, Solaris ou MacOS, existe um único sistema de arquivos e as unidades de disco devem ser montadas a partir de diretórios deste único sistema de arquivos. Isto interfere na especificação dos caminhos. Na plataforma MS-Windows temos que prefixar os caminhos com a identificação da unidade de disco. O caractere separador de nomes em um caminho nas plataformas Unix e derivados é a barra '/' e na plataforma MS-Windows é a barra invertida '\'. Um caminho é absoluto quando o primeiro nome está diretamente contido na raiz (e inclui a unidade de disco no caso da plataforma MS-Windows). 


Acesso a arquivos

 

Antes de podermos ler ou escrever em um arquivo ele deve ser “aberto” (“open”). A função da biblioteca C que faz a “abertura” de um arquivo chama-se fopen(). A função fopen se encarrega de fazer as verificações e inicializações necessárias para as operações de entrada e saída a serem realizadas posteriormente.

*** O ambiente C oferece ao programador três fluxos/arquivos pré-abertos (1) a entrada padrão: stdin (2) o erro padrão stderr e (3) a saída padrão: stdout; Existem alguns vícios de linguagem na literatura de entrada e saída: o ideal seria a distinguir fluxo (stream) e arquivos (files), mas muitas vezes estes termos são usados de maneira inconsistente. Um fluxo pode ser unidirecional ou bidirecional o destino de um fluxo de saída pode ser um arquivo ou uma tela e a origem de um fluxo de entrada também pode ser um arquivo ou um teclado. A origem “default” da entrada padrão é o teclado ligado ao processador de comandos que executou o programa e o destino “default” do erro e da saída padrão é a tela/video ligada ao processador de comandos que disparou a execução do programa.

 

***Tentando falar sem vícios: A função fopen() retorna um ponteiro para uma estrutura que descreve um fluxo (stream) ligado a um arquivo (file).

 

 A função fopen retorna um ponteiro (as vezes denominado ponteiro de arquivo, (file pointer)) que aponta para uma estrutura que contém informação sobre o arquivo correspondente. A informação apontada pelo ponteiro de arquivo compreende a localização de área de armazenamento temporário (“buffer”), posição corrente no arquivo, se o arquivo é para leitura ou escrita, se aconteceram erros, etc.

 

Quando vamos criar um arquivo (do tipo “texto”, que será discutido abaixo) devemos primeiro “abrir” o arquivo, em seguida podemos escrever tantas vezes quantas forem necessárias e ao final “fechamos” o arquivo, conforme a figura a seguir:

 

Exemplo:
    FILE *fp;

    fp=fopen("saida.txt", "w");

    fprintf(fp, "linha 1\nlinha 2");

    fclose(fp);

FILE é um tipo definido em <stdio.h> e no trecho acima fp é uma variável que armazena um ponteiro para uma estrutura em memória que contém informação sobre um arquivo com o nome saida.txt.  A função fopen tem a seguinte definição:

FILE *fopen(char *name, char *mode);
FILE é um tipo definido via typedef (não é uma etiqueta de estrutura!) . O primeiro argumento é uma cadeia de caracteres correspondente ao nome do arquivo. O segundo argumento é o modo, que indica como o arquivo será usado: “r” indica leitura, “w” indica escrita e “a” indica apensar (estender), em alguns sistemas estes modos podem ser diferentes. Todo arquivo pode ser considerado um arquivo binários, mas um arquivo além de ser binário pode atender a alguns requisitos e ser do tipo arquivo de texto. Um arquivo de texto contém dados que obedecem a alguma codificação (normalmente ASCII). O “default” é arquivo do tipo texto e para definir o arquivo como sendo binário é necessário pósfixar o argumento mode com a letra “b” (“b”de binary). Se um arquivo não existe e é aberto para escrita ou extensão então ele é criado se for possível. Se um arquivo existente é aberto para escrita então todo o conteúdo é removido, mas o conteúdo é preservado se for aberto para extensão. Abrir um arquivo não existente para leitura é um erro e vários outro tipos de erro são possíveis. As operações sobre arquivos seguem um modelo onde, de forma implícita, existe uma espécie de “ponteiro” que funciona da seguinte forma: toda vez que o arquivo é aberto o ponteiro fica no início do arquivo. toda vez que é feita uma operação de leitura ou de escrita o “ponteiro” avança no arquivo de forma a indicar a “próxima posição”. O avanço depende da quantidade e do tipo do elemento que é escrito ou lido. Ao se escrever um caractere o ponteiro avança um caractere ou byte ao se escrever um arranjo de 5 ints (int [5]) o ponteiro avança 5*4 (para máquinas que usam 4 bytes por int) bytes e assim por diante.

 

A figura a seguir tenta mostrar alguns dos aspectos que são envolvidos a partir do momento que temos um arquivo aberto cujo acesso é feito pelo ponteiro da estrutura do tipo FILE:

 

A partir de fp temos um bloco de memória no espaço do programa do usuário, a partir deste bloco temos uma ou mais estruturas do sistema operacional – SO que coordena todas as operações de entrada e saída e a partir desta estrutruras do SO temos acesso às sequências de zeros e uns (fluxo, stream) providas pelo “Disco”. A seta preta de fp para um bloco do tipo FILE segue a nossa convenção de desenhos de ponteiros, as demais setas (vermelha, azul) correspondem a conceitos que não são explorados nesta disciplina. A seta tracejada verde é muito importante no entendimento de arquivos.

 

Pode ser observado que há oportunidades para muitos problemas. A função fopen retorna um valor correspondente a um “endereço” e ele deve ser testado e a ação apropriada deve ser tomada caso não seja um endereço adequado (se a fopen() retornar NULL então aconteceu um ou mais problemas). O trecho abaixo ilustra a verificação do valor retornado pela função fopen():
    FILE *fp;

    fp=fopen("saida.txt", "w");
    if(fp==NULL){
      etc etc
      printf(“problemas ao abrir arquivo!\n”);

      etc etc

    }

    fprintf(fp, "linha 1\nlinha 2");

    fclose(fp);

 

 

Uma vez aberto um arquivo de texto podemos ler ou escrever caracteres ou linhas sobre ele, isto pode ser feito, por exemplo, com as funções:

int getc(FILE *fp)
int putc(int c, FILE *fp)
int fscanf(FILE *fp, char *format, …)
int fprintf(FILE *fp, char *format, …)

A função getc retorna o próximo caractere do arquivo apontado por fp. A função getc retorna EOF se encontrar o final do arquivo ou um erro. putc escreve o caractere c no arquivo correspondente a fp e retorna o caractere escrito ou retorna EOF se acontecer um erro. Quando um programa C é iniciado o ambiente C define e abre três arquivos(fluxos) e provê apontadores para eles. Estes arquivos são (i) entrada padrão (ii) saída padrão e (iii) “erro” padrão [ou arquivo padrão para comunicação de erros]. Os apontadores são stdin, stdout, e stderr e são declarados em <stdio.h>. Normalmente stdin é “conectado” ao teclado e stdout e stderr são conectados à “tela da console”. Mas estas conexões podem ser redirecionadas em termos de arquivos e encanamentos(pipes).

fscanf e fprintf são funções idênticas à scanf e printf respectivamente exceto pelo primeiro argumento inexistente nestas últimas. O argumento implícito da scanf é o stdin e o argumento implícito da printf  é o stdout. stdin e stdout são do tipo FILE * e são constantes, não são variáveis, não é possível atribuir valores para eles.

 

A função fclose termina a conexão entre a estrutura em memória apontada por fp e a estrutura externa do sistema de arquivos (Esta conexão é estabelecida pela função fopen):
int fclose(FILE *fp)
A função fclose antes de terminar a conexão faz a descarga (ou esguicho) final dos dados (flush).

Com estas operações básicas podemos escrever um programa que cria um arquivo a partir de outro arquivo:

#include <stdio.h>
#include <stdlib.h>


void copia(FILE *e, FILE *s){
  int c;
  while((c=getc(e))!=EOF ) putc(c,s);
}


int main (void){
  char origem[100], dest[100];
  FILE *entrada, *saida;
  // obter nomes dos arquivos
  printf("Entre com nome do arquivo a ser copiado:");
  scanf("%99s", origem);
  printf("Entre com nome do arquivo de saida:");
  scanf("%99s", dest);
  // abrir arquivos
  if((entrada=fopen(origem, "r"))==NULL){
    printf ("Nao foi possivel acessar o arquivo %s\n", origem);
    system("PAUSE");
    return 1;
  }
  if((saida=fopen(dest, "r"))==NULL){
    if((saida=fopen(dest,"w"))==NULL){
      printf("Nao foi possivel criar arquivo %s\n", dest);
      system("PAUSE");
      return 1;
    }
  }else{
    printf("arquivo %s ja existe!\n", dest);
    system("PAUSE");
    return 1;
  }
  copia(entrada, saida);
  // fecha arquivos
  fclose (entrada);
  fclose (saida);
  printf ("Copia pronta!\n");
  system("PAUSE");     
  return 0;
}

 

Entrada e saída de linhas/caracteres

 

Na memória temos instâncias de int, double, char, arranjo, struct e temos o caso especial de arranjo de caracteres que por convenção de um caractere nulo no final representam uma instância de “string”. No caso de entrada e saída surge o conceito de “linha” (line) uma sequência de caracteres terminada com um caractere denominado “new line”. Não existe uma norma/diretriz especificando que a linha é feita de strings ou que os strings são feitos de linhas. O caractere (ou caracteres) que representa/representam “new line” tipicamente deveriam existir somente nos arquivos.

A biblioteca padrão provê uma função de entrada denominada fgets:
char *fgets(char *line, int maxline, FILE *fp)

A função fgets lê a próxima linha (incluindo o caractere “new line”)  do arquivo apontado por fp atribuindo os caracteres ao arranjo line; e no máximo  maxline-1 caracteres são lidos. A linha lida será terminada com ‘\0’ (de uma certa maneira o caractere “new line” se transforma em caractere nulo!). Se terminar o arquivo será retornado NULL, normalmente fgets retorna line (retorna o endereço de memória do início de line). Para saída a função fputs escreve um string (que pode ou não conter o caractere “nova linha”):
int fputs(char *line, FILE *fp)
fputs retorna EOF se acontecer um erro ou retorna zero caso contrário. Existem funções gets e puts similares que operam sobre stdin e stdout. Infelizmente gets e puts não agem de forma “exatamente” igual a fgets e fputs. A função gets remove o caractere \n (correspondente ao enter do teclado) e a função puts adiciona um \n no final da linha. No livro de K&R isto é avisado de uma maneira meio surpreendente: “confusingly, gets() deletes the terminal ‘\n’ and puts() adds it.” Pelo menos eles admitem: “confusamente”.

 

Reescreva o programa acima para copiar arquivos do tipo texto (arquivos compostos por linhas!), mas antes disso leia o material abaixo sobre arquivos binários e arquivos do tipo texto.


Arquivos do tipo texto/linha/caracteres e arquivos binários

O termo Arquivo de Texto é usado para denotar um arquivo que contém informação que possa ser lida pelo ser-humano usando um editor ou processador de texto. Uma outra definição de Arquivo de Texto, mais abrangente e menos subjetiva: qualquer arquivo cujo conteúdo sejam caracteres válidos em algum tipo de especificação ou padrão. Um arquivo de texto certamente é um arquivo binário, entretanto o termo Arquivo Binário normalmente é usado para denotar os arquivos cuja informação não seja legível pelo ser-humano utilizando um processador ou editor. Outra definição de Arquivo Binário caracteriza qualquer arquivo que não seja formado a partir de códigos de caracteres. Não existem formatos ou gabaritos pré-definidos na linguagem C para arquivos de texto ou binários. A responsabilidade de permitir que o arquivo seja ou não legível pelo ser humano é do programador.

O conceito de texto pode perder a simplicidade na medida em que um texto pode codificar outro texto: o texto entre aspas a seguir: "ção" em algum sistema pode ser codificado como sendo o texto "&ccedil;&atilde;o" ou pode ser codificado em alguma forma de zeros e uns não padronizada. A forma mais simples de escrever arquivos de texto, apesar de todos os avanços da Computação, continua a ser os caracteres da tabela ASCII, pelo menos para quem não é, p. ex., árabe, judeu, chines, coreano, russo, japones.

No ambiente do Dev-C++ podemos utilizar os seguintes “modos” para abertura de arquivos:
Mode                      Meaning
r                  Open a text file for reading
w                  Create a text file for writing
a                  Append to a text file
rb                 Open a binary file for reading
wb                 Open a binary file for writing
ab                 Append to a binary file
r+                 Open a text file for read/write
w+                 Create a text file for read/write
a+                 Append or create a text file for read/write
r+b                Open a binary file for read/write (rb+)
w+b                Create a binary file for read/write (wb+)
a+b                Append a binary file for read/write (ab+)

Funções

A biblioteca padrão apresenta duas importantes funções: fread() e fwrite(). Estas funções podem ler e escrever qualquer tipo de dado, usando qualquer representação. Os protótipos são:

size_t fread(void *buffer, size_t size, size_t num,FILE *fp);
size_t fwrite(void *buffer, size_t size, size_t num, FILE *fp)
;

size_t é um tipo definido no cabeçalho <stddef.h> (via typedef) e corresponde a um tipo (da “família” do tipo int) e é o tipo do valor retornado pela expressão (sizeof <variável>) ou pela expressão (sizeof(<nomedotipo>)).

O programa abaixo cria um arquivo e em seguida à criação (via fopen()) o programa escreve 3 valores do tipo int utilizando o arranjo ai. após a escrita o programa invoca a função rewind() que posiciona o ponteiro de posição no arquivo no início. o programa então lê os 3 valores do tipo int e testa se os valores lidos são os mesmos valores escritos.  Este programa pode servir de modelo para o teste do entendimento dos conceitos de entrada e saída.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]){
  int ai[]={65,66,67};
  int lido[3];
  int N=3;
  FILE *pa;
  pa=fopen("arq.arq", "wb+");
  //escreve o arranjo
  fwrite(ai,sizeof(int), N, pa);
  //reposiciona o ponteiro de posição
  rewind(pa);
  fread(lido, sizeof(int), N, pa);
  if(lido[0]==ai[0]&&lido[1]==ai[1]&&lido[2]==ai[2])
    printf( "deu certo... o que se le e' o que foi escrito!\n");
  else
    printf("deu errado...  tem alguma coisa podre...");
  fclose(pa);
  system("PAUSE");     
  return 0;
}

 

 O programa abaixo usa um arquivo (contador.bin) para manter/persistir um contador do tipo int. O contador mantem um valor correspondente a quantas vezes o programa foi executado. O programa percebe a primeira execução (o programa é executado uma primeira vez) quando ao tentar abrir o arquivo a função fopen() retorna NULL... Observe que a primeira chamada de fopen() usa o modo “r+b” (ler/escrever/etc dados binários). Com este modo “r+b” uma das razões de fopen() retornar  NULL é o arquivo não existir. Se o arquivo não existe então é a primeira execução. Neste caso o arquivo é criado, modo “wb”,  e seu conteúdo corresponde a um valor do tipo int. Se o programa consegue abrir o arquivo então ele lê o valor e grava (por cima do antigo valor) o incremento de 1  do valor lido. A gravação por cima é obtida via rewind() que coloca o apontador de arquivo no início do arquivo.

int main(int argc, char *argv[]){
  FILE *fp;
  fp=fopen("contador.bin", "r+b");
  if(fp==0){
    printf("A primeira execucao a gente nao esquece!\n");
    fp=fopen("contador.bin","wb");
    if(fp==0){
      printf("Problemas na execucao: contacte o suporte e forneca o codigo NCAAPV\n");
      return -1;
    }
    int zero=0;
    fwrite(&zero, sizeof(int),1, fp);
    fclose(fp);
    return 0;
  }else{
    int ii;
    fread(&ii, sizeof(int), 1, fp);
    printf("Esta eh execucao de numero:%d deste programa.\n", ii);
    rewind(fp);
    ii++;
    fwrite(&ii, sizeof(int),1,fp);
    fclose(fp);
  }
  return 0;
}

Um programador pode verificar melhor os erros via variável global “errno”, (cabeçalho “error.h”) e funções ferror(), perror() e strerror().

The fread() function reads from the file associated with fp, num number of objects, each object size bytes long, into buffer pointed to by buffer. It returns the number of objects actually read. If this value is 0, no objects have been read, and either end of file has been encountered or an error has occurred. You can use feof() or ferror() to find out which. Their prototypes are:

int feof(FILE *fp);
int ferror(FILE *fp);

 

The feof() function returns non-0 if the file associated with fp has reached the end of file, otherwise it returns 0. This function works for both binary files and text files. The ferror() function returns non-0 if the file associated with fp has experienced an error, otherwise it returns 0.

The fwrite() function is the opposite of fread(). It writes to file associated with fp, num number of objects, each object size bytes long, from the buffer pointed to by buffer. It returns the number of objects written. This value will be less than num only if an output error as occurred.

The void pointer is a pointer that can point to any type of data without the use of a TYPE cast (known as a generic pointer). The type size_t is a variable that is able to hold a value equal to the size of the largest object supported by the compiler. As a simple example, this program writes an integer value to a file called MYFILE using its internal, binary representation.

#include <stdio.h>  /* header file  */
#include <stdlib.h>
void main(void){

 FILE *fp;   /* file pointer */
 int i;
 /* open file for output */
 if ((fp = fopen("myfile", "w"))==NULL){
  printf("Cannot open file for writing \n");
  exit(1);
 }
 i=100;
 if (fwrite(&i, sizeof(i), 1, fp) !=1){

  printf("Write error occurred");
  exit(1);
 }
 fclose(fp);

 /* open file for input */
 if ((fp =fopen("myfile", "r"))==NULL){
  printf("Opening for read error occurred");
  exit(1);
 }
 printf("i is %d",i);
 fclose(fp);
}

File System Functions:

 

You can erase a file using remove(). Its prototype is

int remove(char *file-name);

You can position a file's current location to the start of the file using rewind(). Its prototype is

void rewind(FILE *fp);


Lista das funções stdio extraidas do dev-c++

Input and Output: <stdio.h>

 

FILE *fopen(const char *filename, const char *mode)

FILE *freopen(const char *filename, const char *mode, FILE *stream)

int fflush(FILE *stream)

int fclose(FILE *stream)

int remove(const char *filename)

int rename(const char *oldname, const char *newname)

 

FILE *tmpfile(void)

char *tmpnam(char s[L_tmpnam])

int setvbuf(FILE *stream, char *buf, int mode, size_t size)

void setbuf(FILE *stream, char *buf)

int fprint(FILE *stream, const char *format, ...)

int sprintf(char *s, const char *format, ...)

vprintf(const char *format, va_list arg)

vfprintf(FILE *stream, const char *format, va_list arg)

vsprintf(char *s, const char *format, va_list arg)

int fscanf(FILE *stream, const char *format, ...)

int scanf(const char *format, ...)

 

int sscanf(char *s, const char *format, ...)

int fgetc(FILE *stream)

char *fgets(char *s, int n, FILE *stream)

int fputc(int c, FILE *stream)

int fputs(const char *s, FILE *stream)

int getc(FILE *stream)

int getchar(void)

char *gets(char *s)

int putc(int c, FILE *stream)

int putchar(int c)

int ungetc(int c, FILE *stream)

size_t fread(void *ptr, size_t size, size_t nobj, FILE *stream)

size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *stream)

 

int fseek(FILE *stream, long offset, int origin)

A função fseek ajusta a <posição corrente no arquivo> uma leitura ou escrita subsequente irá acessar dados utilizando este ajuste. Para um arquivo do tipo binário a <posição> é ajustada offset caracteres a partir de origin. O argumento origin pode ser:
(i)SEEK_SET denotando o início ou
(ii)SEEK_CUR denotando a posição corrente ou
(iii)SEEK_END  denotando o final do arquivo.
Para um arquivo texto offset deve ser 0 ou um valor retornado pela função ftell (e neste caso origin deve ser SEEK_SET). No caso de sucesso no ajuste fseek retorna 0, no caso de algum erro fseek retorna um valor diferente de zero.
---
O programa abaixo cria um arquivo e demonstra o uso de fseek para posicionar neste arquivo. O programa cria um arquivo contendo uma sequência de (5) elementos do tipo double. O arquivo é fechado e logo am seguida é aberto (a posição de entrada e saída é o início do arquivo, mas o uso da função fseek() altera a posição e em seguida é lido um único elemento do tipo double.

#include <stdio.h>
#include <stdlib.h>

int main() {
    #define SIZE 5
    double sai[SIZE] = {1.,2.,3.,4.,5.}; //arranjo para saida
    /* abertura , saida e fechamento de fluxo/arq. */
    FILE * fp = fopen("arq.bin", "wb");
    fwrite(sai,sizeof(double),SIZE,fp);
    fclose (fp);
 
    /* leitura do arquivo criado */
    double entra;
    fp = fopen("arq.bin","rb");
 
    /* Ajusta a posicao de E/S do arquivo.
*/
    #define POSICAO 3L
    if (fseek(fp,sizeof(double)*POSICAO,SEEK_SET) != 0){
      printf("fseek() falhou\n" );
      exit(EXIT_FAILURE);
    }
 
    int resultado = fread(&entra,sizeof(double),1,fp);   /* read one f-p value  */
    printf("%.1f\n", entra);                        /* print one f-p value */
    
     return 0;
}

--------

long ftell(FILE *stream)

A função ftell retorna a <posição corrente no arquivo> ou -1L no caso de erros.

A biblioteca stdio não define uma função que retorna o tamanho de um arquivo. Não é recomendado ler o arquivo do inicio até o final simplesmente para saber o tamanho. Apesar de algumas restrições podemos usar a função de posicionamento (fseek) e a função de posição corrente (ftell) para saber o tamanho do arquivo.

O programa abaixo usa esta “estratégia” para calcular o tamanho do arquivo. Este programa lista os primeiros bytes de um arquivo cujo nome é dado em um diálogo (tosco).

 

#include <stdio.h>
#include <stdlib.h>

unsigned long tamanho(FILE *f){
  if(fseek(f,0, SEEK_END)){
    printf("problema para descobrir tamanho de arquivo\n");
    exit(-1);
  }
  return ftell(f);
}

int main(int argc, char *argv[]){
  char nomeArquivo[200];
  FILE *arq;
  // obter nomes do arquivo
  printf("Entre com nome do arquivo a ser listado:");
  scanf("%99s", nomeArquivo);
  // abrir arquivo
  if((arq=fopen(nomeArquivo, "rb"))==NULL){
    printf ("Nao foi possivel acessar o arquivo %s\n", nomeArquivo);
    system("PAUSE");
    return 1;
  }
  //tamanho do arquivo, várias abordagens...
  int tamArq=tamanho(arq);
  printf("este arquivo tem %d bytes\n", tamArq);
  //obter numero de bytes
  int num;
  printf("Entre com numero de bytes a serem listados:");
  scanf("%d", &num);
  if(num>tamArq) num=tamArq;
  //lista bytes.
 
printf("listar %d bytes\n", num);
  fseek(arq, 0, SEEK_SET);
  printf("ok\n");
  int ii; int byte;
  for(ii=0; ii<num; ii++){
    fread(&byte, 1, 1, arq);
    printf("%04X:%02X %c\n", ii, (char)byte, isprint(byte)?byte:' ' );
  } 
  system("PAUSE");
  return 0;
}


Este programa corresponde a o que é chamado um “dump” das primeiras posições (bytes) de um arquivo. É um programa com objetivos didáticos (não testa por exemplo o valor retornado por fread()). Na interação com o usuário o programa lê um nome de arquivo e a quantidade de bytes que devem ser listados (esta quantidade deve ser limitada pelo tamanho do arquivo). Observe o uso dos códigos SEEK_END e SEEK_SET. Exercício: escreva uma nova versão deste programa para listar os últimos n bytes de um arquivo.Outro exercício: reescreva o programa acima de modo a não ter que calcular o tamanho do arquivo usando a função fseek; este novo programa deve verificar o valor retornado pela função fread() e terminar a listagem quando fread() retornar 0 (fread() retorna número de “objetos” lidos).

 

void rewind(FILE *stream)

A chamada rewind(fp) equivale a fseek(fp, 0L, SEEK_SET); clearerr(fp);

 

int fgetpos(FILE *stream, fpos_t *ptr)

int fsetpos(FILE *stream, const fpos_t *ptr)

A função fgetpos registra a posição corrente para uso subsequente pela função fsetpos

 

void clearerr(FILE *stream)

int feof(FILE *stream)

int ferror(FILE *stream)

void perror(const char *s)

 

http://en.wikibooks.org/wiki/C_Programming/Standard_libraries

 

O programa abaixo cria um arquivo com o nome arq.arq, em seguida escreve um valor do tipo int (4 bytes) e fecha o arquivo. Na sequência o arquivo arq.arq é aberto para leitura (reutilizando o apontador fp) e a leitura é feita caractere a caractere (byte a byte). Os 4 bytes são exibidos utilizando o formato de conversão X (hexadecimal), mostrando se a ordem é 0A0B0C0D ou 0D0C0B0A (little endian ou big endian).

#include <stdio.h>

#include <stdlib.h>

 

int main(int argc, char *argv[])

{

  FILE *fp;

  fp=fopen("arq.arq", "w");

  if(fp==NULL){

    printf("problemas ao abrir para escrever!\n");

    return 0;

  }

  int n=0x0A0B0C0D;

  fwrite(&n, sizeof(int), 1, fp);

  fclose(fp);

  fp=fopen("arq.arq", "r");

  if(fp==NULL){

    printf("problemas ao abrir para ler!\n");

    return 0;

  }

  char buf[4];

  int i;

  for(i=0; i<4; i++) buf[i]=getc(fp);

  for(i=0; i<4; i++) printf("%X ", buf[i]);

  printf("\n");

  system("PAUSE");     

  return 0;

}

 

O programa abaixo exemplifica entrada e saída de elementos agregados. Para criar o arquivo é usada uma estrutura, o arquivo é fechado e em seguida é aberto para ser lido como os componentes da estrutura!

 

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  struct etiq {int ai[10]; char ac[80];};
  struct etiq e1;
  int i;
  for(i=0; i<10; i++) e1.ai[i]=0x41424344;
  for(i=0; i<80; i++) e1.ac[i]='A';
  FILE *fp;
  fp=fopen("tstESstruct.bin", "w");
  if (fwrite(&e1, sizeof(struct etiq), 1, fp) !=1){ //grava os dois arranjos
    printf("Erro de gravacao!!!");
    exit(1);
  }
  fclose(fp);
  int ai2[10]; char ac2[80];
  fp=fopen("tstESstruct.bin", "r");
  fread(&ai2[0], sizeof(int), 10, fp); //le 10 int’s
  fread(&ac2[0], sizeof(char), 80, fp); //le 80 caracteres
 
  for(i=0; i<10; i++) printf("%X ", ai2[i]);
  printf("\n");
  for(i=0; i<80; i++) printf("%c", ac2[i]);
  fclose(fp);
 
  system("PAUSE");     
  return 0;
}

 

O programa abaixo utiliza a função fseek() que permite posicionar de forma arbitrária na extensão do arquivo. Inspecione o programa e tente entendê-lo:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
   
    FILE *fp;
    fp=fopen("saida.txt", "w");
    fprintf(fp, "linha 1\nlinha 2");
    fclose(fp);
    fp=fopen("saida.txt", "r");
    fseek(fp,10L, SEEK_SET); //posiciona no decimo byte!
 
// SEEK_SET inicio SEEK_CUR corrente SEEK_END final
    char buf[200];
    fgets(buf, 100, fp);
    printf("%s\n", buf);
    fclose(fp);
  system("PAUSE");     
  return 0;
}

Faça um programa que leia um arquivo do tipo texto e faça a atribuição dos elementos correspondentes a uma representação de sudoku com arranjo da forma abaixo; faça também a operação inversa de criar o arquivo a partir de um arranjo já inicializado.

    int s[9][9]={{5,3,4,6,7,8,9,1,2},
                 {6,7,2,1,9,5,3,4,8},
                 {1,9,8,3,4,2,5,6,7},
                 {8,5,9,7,6,1,4,2,3},
                 {4,2,6,8,5,3,7,9,1},
                 {7,1,3,9,2,4,8,5,6},
                 {9,6,1,5,3,7,2,8,4},
                 {2,8,7,4,1,9,6,3,5},
                 {3,4,5,2,8,6,1,7,9}};

Ao invés de ser inicializado deverá ser lido. Experimente com diferentes formatos. Seguem duas sugestões:
Formato 1:

5,3,4,6,7,8,9,1,2
6,7,2,1,9,5,3,4,8
1,9,8,3,4,2,5,6,7
8,5,9,7,6,1,4,2,3
4,2,6,8,5,3,7,9,1
7,1,3,9,2,4,8,5,6
9,6,1,5,3,7,2,8,4
2,8,7,4,1,9,6,3,5
3,4,5,2,8,6,1,7,9

Formato 2
534678912
672195348
198342567
859761423
426853791
713924856
961537284
287419635
345286179