Aula 11 - Arquivos Texto e Binários
Até agora, todos os dados dos nossos programas existiam apenas enquanto o programa estava em execução: ao fechar o programa, tudo se perdia. Nesta aula, veremos como gravar e recuperar dados em arquivos, permitindo que informações sobrevivam entre execuções.
1. Por que usar arquivos?
Um arquivo é um conjunto de dados armazenado em memória secundária (disco rígido, SSD, etc.). Diferentemente da memória principal (RAM), que vimos desde a Aula 1 e é volátil e rápida, a memória secundária é persistente: os dados sobrevivem mesmo após desligar o computador.
| Memória Principal (RAM) | Memória Secundária (Disco) | |
|---|---|---|
| Velocidade | Rápida | Lenta (em comparação) |
| Persistência | Volátil — dados são perdidos ao desligar | Persistente — dados sobrevivem |
| Capacidade | Limitada (gigabytes) | Grande (terabytes) |
| Uso típico | Executar programas, dados temporários | Armazenar dados permanentemente |
Arquivos são, no fundo, apenas sequências de bytes. Não importa se chamamos um arquivo de "texto" ou "binário": internamente, tudo são bytes (números de 0 a 255). A diferença está inteiramente em como interpretamos esses bytes:
- Arquivos de texto: os bytes representam caracteres (ASCII ou UTF-8), e por isso são legíveis diretamente por humanos, em qualquer editor de texto. Exemplos:
.txt,.csv,.html. - Arquivos binários: os bytes representam dados em um formato específico (números em sua representação interna, structs completas, dados compactados), sem relação direta com caracteres legíveis. Exemplos:
.jpg,.mp3,.exe.
Por que essa distinção importa na prática? Um número inteiro como 12345, guardado em um arquivo de texto, ocupa 5 bytes (um por dígito, como caracteres '1', '2', ...). O mesmo número, guardado em um arquivo binário como int, ocupa sempre 4 bytes (o tamanho de um int em memória), independente de quantos dígitos ele tenha. Arquivos binários são mais compactos e mais rápidos de ler/escrever (não é preciso converter texto para número e vice-versa), mas exigem que quem for ler o arquivo saiba exatamente o formato usado — não dá para simplesmente abrir e ler visualmente.
|
2. Abrindo e fechando arquivos
Para trabalhar com arquivos em C, usamos um ponteiro do tipo FILE (um tipo definido em <stdio.h>, que representa um arquivo aberto). A função fopen() abre um arquivo e retorna um ponteiro para ele — sempre verifique se essa abertura foi bem-sucedida, checando se o retorno é NULL.
#include <stdio.h> int main() { // Abrindo um arquivo em modo leitura FILE *arquivo = fopen("dados.txt", "r"); // Verificando se abriu corretamente if (arquivo == NULL) { printf("Erro: nao foi possivel abrir o arquivo\n"); return 1; } // ... operações com o arquivo ... // Fechando sempre ao final fclose(arquivo); return 0; }
O segundo argumento de fopen() é o modo de abertura, que diz o que pretendemos fazer com o arquivo:
| Modo | Significado |
|---|---|
"r" |
Leitura (o arquivo precisa já existir). |
"w" |
Escrita (cria um novo arquivo, ou apaga o conteúdo de um já existente). |
"a" |
Anexação — escreve sempre ao final do arquivo, sem apagar o que já existia. |
"r+" |
Leitura e escrita (o arquivo precisa já existir). |
"w+" |
Leitura e escrita (cria um novo arquivo, ou apaga o conteúdo de um já existente). |
"rb", "wb", "ab" |
Os mesmos modos acima, mas em formato binário (acrescentando um b). |
3. Operações gerais com arquivos
As funções desta seção funcionam com qualquer tipo de arquivo, texto ou binário. Todas elas giram em torno de um conceito central: o cursor do arquivo.
A struct FILE. Quando chamamos fopen(), o que recebemos de volta é um ponteiro para uma struct chamada FILE — definida internamente pela biblioteca padrão de C, e não por nós. Diferente das structs que criamos nas Aulas 10 e 11 (Aluno, Ponto, Funcionario), não temos acesso direto aos membros de FILE: é uma struct opaca — sabemos que ela existe e o que ela representa, mas seus detalhes internos são escondidos de propósito, e só manipulamos esse arquivo através das funções prontas que a biblioteca oferece (fread, fwrite, fseek, etc.), nunca acessando seus membros diretamente com . ou ->.
|
Ainda assim, é útil imaginar como uma FILE poderia ser implementada por dentro, para entender o que está de fato acontecendo quando chamamos essas funções. Uma versão simplificada (e só didática — a implementação real varia entre bibliotecas, e é bem mais complexa) poderia se parecer com isto:
// Versão didática e simplificada — NÃO é o código real da biblioteca! typedef struct { int descritor; // identifica o arquivo aberto para o sistema operacional long cursor; // posição atual (o "current file position") unsigned char buffer[4096]; // bytes lidos/a escrever, guardados temporariamente int modo; // leitura, escrita, binário, etc. (definido no fopen) int eof; // virou verdadeiro quando o cursor passa do fim do arquivo } FILE;
O nome cursor, aqui, é a nossa forma didática de nos referirmos ao que a documentação oficial chama de current file position — "posição atual no arquivo" — o único membro desta lista que realmente importa para o que vamos discutir nesta aula. É esse número que:
- Começa em
0ao abrir um arquivo em modo"r"ou"w". - Avança automaticamente, byte a byte, a cada leitura ou escrita — sem que precisemos fazer nada manualmente.
- É apenas consultado (sem ser alterado) por
ftell(arquivo). - É alterado diretamente por
fseek(arquivo, ...)erewind(arquivo), "pulando" para qualquer posição, sem precisar ler ou escrever nada no caminho.
Com isso em mente, as principais funções gerais são:
void rewind(FILE *arquivo); — volta o cursor para o início do arquivo (posição 0). Equivale a fseek(arquivo, 0, SEEK_SET).
long ftell(FILE *arquivo); — retorna a posição atual do cursor, em bytes a partir do início do arquivo. Retorna -1 se houver erro.
int fseek(FILE *arquivo, long deslocamento, int origem); — move o cursor para uma posição específica. O parâmetro origem pode ser:
SEEK_SET— a partir do início do arquivo (deslocamentoé a posição absoluta).SEEK_CUR— a partir da posição atual do cursor (deslocamentoé relativo).SEEK_END— a partir do fim do arquivo (deslocamentogeralmente negativo).
fseek retorna 0 se bem-sucedido, e um valor diferente de zero se houver erro.
int fflush(FILE *arquivo); — força a gravação imediata de tudo o que ainda estiver no buffer interno, sem esperar o momento "natural" em que isso aconteceria. Útil quando é preciso garantir que os dados já foram fisicamente escritos antes de continuar.
int feof(FILE *arquivo); — retorna verdadeiro (diferente de zero) se o cursor já passou do fim do arquivo, e falso (zero) caso contrário. É comum (mas, como veremos, arriscado) usá-la em laços como while (!feof(arquivo)) para ler até o final.
Além dessas, existem outras funções úteis que atuam sobre o arquivo como um todo (e não sobre o seu conteúdo), como remove(), que apaga um arquivo do disco, e rename(), que renomeia (ou move) um arquivo — ambas recebem apenas o(s) nome(s) do arquivo, sem precisar de um FILE * aberto. Não entraremos em detalhes sobre elas aqui, mas vale saber que existem, caso você precise gerenciar arquivos além de apenas ler e escrever seu conteúdo.
#include <stdio.h> int main() { char buffer[256]; FILE *arquivo = fopen("dados.txt", "r"); if (arquivo == NULL) { return 1; } // rewind: garante que o cursor comeca no inicio rewind(arquivo); printf("Posicao apos rewind: %ld\n", ftell(arquivo)); // 0 // fseek: posiciona em um lugar especifico fseek(arquivo, 10, SEEK_SET); // vai para o byte 10, a partir do inicio printf("Posicao agora: %ld\n", ftell(arquivo)); // 10 fseek(arquivo, 5, SEEK_CUR); // avanca 5 bytes a partir da posicao atual printf("Posicao apos avancar 5: %ld\n", ftell(arquivo)); // 15 // feof com laco de leitura rewind(arquivo); while (!feof(arquivo)) { if (fgets(buffer, 256, arquivo) != NULL) { printf("%s", buffer); } } fclose(arquivo); return 0; }
Repare, nesse exemplo, exatamente como cursor muda a cada chamada: começa em 0 (após o rewind), pula para 10 (após o fseek absoluto), e depois para 15 (após o fseek relativo, 10 + 5) — e é sempre o valor de cursor, nunca outra coisa, que ftell nos devolve a cada chamada.
O que acontece se eu escrever no meio do arquivo? Uma dúvida comum: se eu usar fseek para levar cursor para o meio de um arquivo já existente e escrever algo ali, o conteúdo que já estava depois é "empurrado" para frente, para abrir espaço? Não. Escrever em uma posição já existente sobrescreve os bytes que estavam ali, byte a byte, exatamente como acontece ao escrever em uma posição de um vetor — não existe, em C, uma operação de "inserir no meio" de um arquivo. Arquivos não têm a capacidade de "abrir espaço" automaticamente; eles só crescem quando escrevemos além do seu tamanho atual (ou quando usamos o modo "a", que sempre escreve no fim).
|
#include <stdio.h> int main() { FILE *arquivo = fopen("dados.txt", "w"); if (arquivo == NULL) { return 1; } fprintf(arquivo, "AAAAAAAAAA"); // 10 bytes: "AAAAAAAAAA" fclose(arquivo); // Reabrindo para escrever no MEIO do arquivo arquivo = fopen("dados.txt", "r+"); // leitura + escrita, sem apagar o conteudo if (arquivo == NULL) { return 1; } fseek(arquivo, 3, SEEK_SET); // cursor = 3 (o quarto byte) fprintf(arquivo, "BBB"); // sobrescreve 3 bytes, a partir da posicao 3 fclose(arquivo); // Conteudo final de "dados.txt": "AAABBBAAAA" (10 bytes, mesmo tamanho de antes!) return 0; }
O arquivo, que tinha 10 bytes ("AAAAAAAAAA"), continua com exatamente 10 bytes depois da segunda escrita — apenas os 3 bytes a partir da posição 3 foram substituídos por "BBB", resultando em "AAABBBAAAA". Nada foi empurrado; os bytes que vinham depois ("AAAA", no final) simplesmente foram sobrescritos onde o cursor passou por cima deles. Se quiséssemos, de fato, inserir conteúdo no meio sem perder nada, seria necessário reescrever o arquivo inteiro "na mão": ler tudo, montar o novo conteúdo (com a parte inserida) em memória ou em um arquivo temporário, e gravar esse resultado por completo.
4. Leitura e escrita em arquivos binários
Para arquivos binários, usamos duas funções específicas: fread() (leitura) e fwrite() (escrita). Diferente das funções de texto que veremos na próxima seção, elas não convertem nada para caracteres — trabalham diretamente com blocos de bytes brutos, copiando exatamente o conteúdo da memória para o arquivo (ou vice-versa).
fwrite: escrevendo blocos de bytes
Sintaxe genérica:
size_t fwrite(const void *buffer, size_t tamanho, size_t quantidade, FILE *arquivo);
| Parâmetro | Significado |
|---|---|
buffer |
Endereço de onde os dados serão lidos na memória, para serem gravados no arquivo (por exemplo, &variavel, ou o próprio nome de um vetor). |
tamanho |
Tamanho, em bytes, de cada elemento a ser escrito — quase sempre obtido com sizeof(tipo). |
quantidade |
Quantos elementos (cada um com tamanho bytes) devem ser escritos. |
arquivo |
O FILE * já aberto (em um modo binário de escrita, como "wb") para onde os dados vão. |
fwrite retorna a quantidade de elementos efetivamente escritos (que deveria ser igual a quantidade, se tudo correu bem).
fread: lendo blocos de bytes
Sintaxe genérica:
size_t fread(void *buffer, size_t tamanho, size_t quantidade, FILE *arquivo);
Os parâmetros são análogos aos de fwrite, mas com o sentido invertido:
| Parâmetro | Significado |
|---|---|
buffer |
Endereço na memória para onde os dados lidos do arquivo serão copiados (o destino da leitura) — por exemplo, &variavel, ou o próprio nome de um vetor. |
tamanho |
Tamanho, em bytes, de cada elemento a ser lido — novamente, quase sempre sizeof(tipo). |
quantidade |
Quantos elementos devem ser lidos. |
arquivo |
O FILE * já aberto (em um modo binário de leitura, como "rb") de onde os dados vêm. |
fread retorna a quantidade de elementos efetivamente lidos — se esse valor for menor do que o pedido em quantidade, significa que o arquivo acabou (ou ocorreu algum erro) antes de conseguirmos ler tudo o que queríamos. É sempre importante checar esse retorno, em vez de assumir que a leitura sempre dá certo.
Vejamos as duas funções em ação, escrevendo e depois lendo uma struct inteira de uma só vez:
#include <stdio.h> typedef struct { int id; char nome[100]; float salario; } Funcionario; int main() { Funcionario func = {1, "Joao", 5000.0}; Funcionario funcLido; size_t elementosLidos; // Escrevendo a struct inteira, em uma unica chamada FILE *arquivo = fopen("dados.bin", "wb"); if (arquivo == NULL) { return 1; } // fwrite: escreve 1 bloco de sizeof(Funcionario) bytes fwrite(&func, sizeof(Funcionario), 1, arquivo); fclose(arquivo); // Lendo a struct de volta arquivo = fopen("dados.bin", "rb"); if (arquivo == NULL) { return 1; } // fread: le 1 bloco de sizeof(Funcionario) bytes elementosLidos = fread(&funcLido, sizeof(Funcionario), 1, arquivo); if (elementosLidos == 1) { printf("ID: %d, Nome: %s, Salario: %.2f\n", funcLido.id, funcLido.nome, funcLido.salario); } fclose(arquivo); return 0; }
Por que gravar a struct inteira de uma vez? Como fwrite/fread trabalham com blocos de bytes brutos, é possível gravar (ou ler) uma struct inteira — com todos os seus membros — em uma única chamada, informando sizeof(Funcionario) como o tamanho de cada "elemento". Isso é muito mais direto do que escrever membro a membro com funções de texto, e garante que a struct volte exatamente como estava, sem risco de erro de formatação na conversão.
|
5. Leitura e escrita em arquivos de texto
Para arquivos de texto, existem quatro funções principais, cada uma pensada para um tipo de leitura ou escrita diferente: fprintf, fscanf, fgets e fgetc.
fprintf: escrita formatada
Funciona exatamente como o printf que já usamos desde a Aula 1, com um parâmetro a mais no início: o arquivo de destino.
Sintaxe genérica:
int fprintf(FILE *arquivo, const char *formato, ...);
arquivo é o FILE * de destino (aberto em modo de escrita, como "w"); formato e os argumentos seguintes funcionam exatamente como no printf (especificadores %d, %s, %f, etc.). A função retorna a quantidade de caracteres escritos (ou um valor negativo, se houver erro).
Na prática, fprintf sozinho já resolve qualquer escrita em arquivo de texto que você precisar nesta disciplina — de um único caractere a uma linha inteira formatada. A biblioteca padrão também oferece fputc(caractere, arquivo) (escreve um único caractere) e fputs(string, arquivo) (escreve uma string, sem formatação), mas não vamos usá-las aqui: qualquer coisa que elas fazem, fprintf também faz, então basta se lembrar dela.
fscanf: leitura formatada
É o equivalente do scanf para arquivos: em vez de ler do teclado, lê do arquivo indicado.
Sintaxe genérica:
int fscanf(FILE *arquivo, const char *formato, ...);
Os parâmetros seguem a mesma lógica do scanf (variáveis passadas por endereço com &, exceto vetores de char). A função retorna a quantidade de itens lidos com sucesso — se esse número for menor do que o esperado, é sinal de que algo deu errado (ou o arquivo acabou) antes de todos os valores serem lidos.
Cuidado com fscanf em char[]. Assim como o scanf (Aula 5), o especificador %s em fscanf não verifica o tamanho do vetor de destino — um arquivo com uma palavra maior do que o vetor reservado causa o mesmo problema de buffer overflow já discutido anteriormente. Prefira fgets quando o tamanho da entrada não for controlado.
|
fgets: lendo uma linha inteira
Sintaxe genérica:
char *fgets(char *destino, int tamanho, FILE *arquivo);
| Parâmetro | Significado |
|---|---|
destino |
Vetor de char onde a linha lida será armazenada. |
tamanho |
Tamanho máximo a ser lido — fgets lê, no máximo, tamanho - 1 caracteres, sempre deixando espaço para o '\0' final. É por isso que ela é segura: nunca escreve além do vetor informado. |
arquivo |
O FILE * de onde a linha será lida. |
fgets lê até encontrar uma quebra de linha ('\n') ou atingir o limite de tamanho - 1 caracteres, o que vier primeiro — e, diferente do que fizemos manualmente na Aula 5, inclui o '\n' na string resultante, caso ele tenha sido encontrado. A função retorna o próprio destino em caso de sucesso, ou NULL se não conseguir ler nada (por exemplo, se o arquivo já tiver acabado).
fgetc: lendo um único caractere
Sintaxe genérica:
int fgetc(FILE *arquivo);
Lê exatamente um caractere do arquivo e o devolve como int (não como char) — isso é proposital: assim, além de qualquer caractere válido, a função também consegue devolver o valor especial EOF ("end of file", fim de arquivo) para sinalizar que não há mais nada para ler, algo que não caberia em um char comum sem ambiguidade.
Juntando as quatro funções em um único exemplo:
#include <stdio.h> int main() { FILE *arquivo; char linha[256]; int caractere; char nome[50]; int idade; // fprintf: escrita formatada em arquivo arquivo = fopen("output.txt", "w"); if (arquivo == NULL) { return 1; } fprintf(arquivo, "%s %d\n", "Maria", 30); fclose(arquivo); // fgets: le a linha inteira arquivo = fopen("output.txt", "r"); if (arquivo == NULL) { return 1; } if (fgets(linha, 256, arquivo) != NULL) { printf("Linha lida com fgets: %s", linha); // ja contem o '\n' } fclose(arquivo); // fscanf: le dados formatados (nome e idade separados) arquivo = fopen("output.txt", "r"); fscanf(arquivo, "%s %d", nome, &idade); printf("Lido com fscanf -> nome: %s, idade: %d\n", nome, idade); fclose(arquivo); // fgetc: le caractere por caractere, ate o fim do arquivo arquivo = fopen("output.txt", "r"); printf("Caracteres: "); caractere = fgetc(arquivo); while (caractere != EOF) { printf("[%c]", caractere); caractere = fgetc(arquivo); } printf("\n"); fclose(arquivo); return 0; }
Resumo: boas práticas com arquivos.
|
Arquivos de texto × arquivos binários: comparativo
Depois de ver as duas formas na prática, vale reunir num só lugar quando cada uma costuma ser a melhor escolha:
| Arquivos de texto | Arquivos binários | |
|---|---|---|
| Legibilidade | Vantagem: podem ser abertos e lidos em qualquer editor de texto comum. | Desvantagem: não são legíveis diretamente — é preciso um programa que saiba interpretar o formato. |
| Tamanho ocupado | Desvantagem: números ocupam um byte por dígito (mais um separador), o que costuma gerar arquivos maiores. | Vantagem: cada valor ocupa exatamente o tamanho do seu tipo em memória (ex.: 4 bytes por int), gerando arquivos mais compactos. |
| Velocidade de leitura/escrita | Desvantagem: exige converter entre texto e número a cada leitura/escrita (fprintf/fscanf). |
Vantagem: grava/lê os bytes exatamente como estão na memória, sem conversão — mais rápido, especialmente para grandes volumes de dados. |
| Portabilidade entre programas/sistemas | Vantagem: um formato de texto bem definido (como .csv) pode ser lido por praticamente qualquer programa, em qualquer sistema. |
Desvantagem: detalhes como tamanho exato dos tipos ou ordem dos bytes podem variar entre compiladores/sistemas diferentes, dificultando a leitura em outro ambiente. |
| Facilidade de edição manual | Vantagem: pode ser corrigido "na mão", em qualquer editor, sem precisar de um programa especial. | Desvantagem: editar diretamente (fora de um programa) é praticamente inviável, por não ser legível. |
Na prática: prefira arquivos de texto quando a legibilidade humana e a portabilidade importarem mais (arquivos de configuração, dados para serem abertos em outras ferramentas, planilhas). Prefira arquivos binários quando o desempenho e o tamanho do arquivo forem prioridade, e o arquivo só precisar ser lido pelo seu próprio programa (ou por outro que conheça exatamente o mesmo formato).
Resumo
|
Exercícios
Nível fácil
-
Escreva um programa que abra um arquivo
"saida.txt"em modo escrita e grave, usandofprintf, três linhas com números de 1 a 3. Feche o arquivo ao final. Verifique sefopenretornouNULL. -
Escreva um programa que abra o arquivo criado no exercício anterior em modo leitura e imprima, na tela, todo o seu conteúdo usando
fgetsem um laço. -
Escreva um programa que abra um arquivo de texto e conte quantos caracteres ele possui, lendo um de cada vez com
fgetcaté encontrarEOF.
Nível médio
-
Escreva um programa que peça ao usuário um nome e uma idade, e grave essas informações em um arquivo de texto usando
fprintf. Em seguida, no mesmo programa, reabra o arquivo e leia os valores de volta comfscanf, imprimindo-os na tela para conferência. -
Defina uma struct
Produto(nome, preço). Escreva um programa que grave um vetor de 3 produtos em um arquivo binário usandofwrite(uma única chamada, escrevendo os 3 de uma vez), e depois leia esse vetor de volta comfread, imprimindo cada produto lido. -
Explique, sem executar o código, qual será o conteúdo de
"copia.txt"ao final do programa abaixo, e por quê:#include <stdio.h> int main() { FILE *saida = fopen("copia.txt", "w"); fprintf(saida, "Primeira linha\n"); fclose(saida); saida = fopen("copia.txt", "w"); fprintf(saida, "Segunda linha\n"); fclose(saida); return 0; }
Nível difícil
-
Escreva um programa que leia um arquivo de texto e grave, em um segundo arquivo, apenas as linhas com mais de 10 caracteres (use
fgetspara ler linha por linha,strlenpara medir o tamanho, efprintfpara gravar as linhas selecionadas). -
Escreva um programa que grave um vetor de 5 inteiros em um arquivo binário, depois use
fseekpara reposicionar o cursor diretamente sobre o terceiro elemento (sem ler os dois primeiros), leia apenas esse valor comfread, e o imprima. Lembre-se de que o deslocamento emfseeké sempre medido em bytes, não em "elementos".
Sugestões de Respostas dos Exercícios
Exercício 1
#include <stdio.h> int main() { int i; FILE *arquivo = fopen("saida.txt", "w"); if (arquivo == NULL) { printf("Erro ao abrir arquivo\n"); return 1; } for (i = 1; i <= 3; i++) { fprintf(arquivo, "%d\n", i); } fclose(arquivo); return 0; }
Exercício 2
#include <stdio.h> int main() { char linha[256]; FILE *arquivo = fopen("saida.txt", "r"); if (arquivo == NULL) { printf("Erro ao abrir arquivo\n"); return 1; } while (fgets(linha, 256, arquivo) != NULL) { printf("%s", linha); } fclose(arquivo); return 0; }
Exercício 3
#include <stdio.h> int main() { int caractere; int contador = 0; FILE *arquivo = fopen("saida.txt", "r"); if (arquivo == NULL) { printf("Erro ao abrir arquivo\n"); return 1; } caractere = fgetc(arquivo); while (caractere != EOF) { contador++; caractere = fgetc(arquivo); } printf("Total de caracteres: %d\n", contador); fclose(arquivo); return 0; }
Exercício 4
#include <stdio.h> int main() { char nome[50]; int idade; FILE *arquivo; printf("Digite seu nome: "); scanf("%s", nome); printf("Digite sua idade: "); scanf("%d", &idade); arquivo = fopen("pessoa.txt", "w"); if (arquivo == NULL) { return 1; } fprintf(arquivo, "%s %d\n", nome, idade); fclose(arquivo); arquivo = fopen("pessoa.txt", "r"); if (arquivo == NULL) { return 1; } fscanf(arquivo, "%s %d", nome, &idade); fclose(arquivo); printf("Lido do arquivo -> nome: %s, idade: %d\n", nome, idade); return 0; }
Exercício 5
#include <stdio.h> typedef struct { char nome[50]; float preco; } Produto; int main() { Produto produtos[3] = { {"Caneta", 2.50}, {"Caderno", 15.90}, {"Borracha", 1.20} }; Produto lidos[3]; int i; FILE *arquivo; arquivo = fopen("produtos.bin", "wb"); if (arquivo == NULL) { return 1; } fwrite(produtos, sizeof(Produto), 3, arquivo); fclose(arquivo); arquivo = fopen("produtos.bin", "rb"); if (arquivo == NULL) { return 1; } fread(lidos, sizeof(Produto), 3, arquivo); fclose(arquivo); for (i = 0; i < 3; i++) { printf("%s: R$ %.2f\n", lidos[i].nome, lidos[i].preco); } return 0; }
Exercício 6
O conteúdo final será apenas "Segunda linha" — sem nenhum vestígio de "Primeira linha". Isso acontece porque o modo "w" sempre sobrescreve o conteúdo do arquivo: cada chamada a fopen(..., "w") apaga o que já existia e começa um arquivo novo (vazio). A segunda abertura em modo "w" não "acrescenta" à primeira — ela recomeça do zero. Se a intenção fosse manter as duas linhas, seria preciso abrir o arquivo em modo "a" (anexação) na segunda vez, ou escrever as duas linhas antes de fechar o arquivo pela primeira vez.
Exercício 7
#include <stdio.h> #include <string.h> int main() { char linha[256]; FILE *entrada; FILE *saida; entrada = fopen("entrada.txt", "r"); if (entrada == NULL) { return 1; } saida = fopen("grandes.txt", "w"); if (saida == NULL) { fclose(entrada); return 1; } while (fgets(linha, 256, entrada) != NULL) { if (strlen(linha) > 10) { fprintf(saida, "%s", linha); } } fclose(entrada); fclose(saida); return 0; }
Exercício 8
#include <stdio.h> int main() { int valores[5] = {10, 20, 30, 40, 50}; int terceiro; FILE *arquivo; arquivo = fopen("numeros.bin", "wb"); if (arquivo == NULL) { return 1; } fwrite(valores, sizeof(int), 5, arquivo); fclose(arquivo); arquivo = fopen("numeros.bin", "rb"); if (arquivo == NULL) { return 1; } // terceiro elemento = indice 2, cada int ocupa sizeof(int) bytes fseek(arquivo, 2 * sizeof(int), SEEK_SET); fread(&terceiro, sizeof(int), 1, arquivo); printf("Terceiro elemento: %d\n", terceiro); // 30 fclose(arquivo); return 0; }