Projeto Estoque da Lojinha
A Ana tem uma lojinha de papelaria e anota o estoque num caderno: quantos cadernos, canetas e borrachas ainda tem, e quanto custa cada um. Neste projeto ela troca o caderno por um programa em C que lembra de tudo de um dia para o outro. O segredo é um arquivo binário: o programa copia as fichas dos produtos da memória para o disco com fwrite e, na próxima vez, copia de volta com fread.
- Desenhar uma struct que pode ir para o disco: com
int32_te o preço em centavos. - Gravar um array de structs com
fwritee ler de volta comfread(Arquivos Binários), conferindo o que eles devolvem. - Olhar os bytes do arquivo com
xxde achar cada campo. - Fazer um menu que lê linhas com fgets e converte números com strtol e strtod.
- Arredondar dinheiro sem perder um centavo.
- Pôr um cabeçalho no arquivo (assinatura e versão) e salvar com segurança, num arquivo temporário trocado com
rename.
O que vamos construir
O programa pronto, na máquina da Ana. Ele abre, carrega o estoque que estava salvo, ela cadastra a cola que chegou, vende 5 canetas e sai. Na próxima vez, tudo continua lá:
$ ./estoque === Estoque da Lojinha === 4 produtos carregados de estoque.bin. 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Salvar 6) Desfazer 0) Sair Opção: 1 Cód Produto Qtd Preço --- ----------------------- ---- ---------- 1 Caderno 10 R$ 15,90 2 Caneta 25 R$ 2,50 3 Borracha 23 R$ 1,75 4 Apontador 8 R$ 4,35 Valor do estoque: R$ 296,55 Opção: 2 Nome: Cola Quantidade: 10 Preço (R$): 3,50 Cadastrado: 5 - Cola Opção: 4 Código: 2 Quantas unidades? 5 Caneta: agora são 20. Opção: 4 Código: 4 Quantas unidades? 9 Só há 8 de Apontador. Opção: 0 Salvei 5 produtos em estoque.bin. Até amanhã!
- A ficha do produto e o primeiro arquivo (
fwriteefread). - O menu e o cadastro (e o centavo que sumia).
- Entrada, saída, salvar e desfazer.
- Um cabeçalho no arquivo e salvar sem perder nada.
Antes de começar
Você precisa ter visto Structs, typedef, Arquivos (fopen e fclose) e, principalmente, Arquivos Binários, que explica o fwrite e o fread argumento por argumento. O arquivo vai se chamar estoque.c:
$ mkdir -p c/estoque $ cd c/estoque
Versão 1 · A ficha do produto e o primeiro arquivo
Pense numa caixa de fichas de papelão, todas do mesmo tamanho: cada produto tem a sua, com os mesmos campos no mesmo lugar. Em C, a ficha é uma struct e a caixa é um array. Gravar o estoque é copiar a caixa inteira para o disco; ler é copiar de volta.
Antes do código, duas decisões sobre a ficha, porque ela vai parar no disco:
- Os números são
int32_t, e nãointoulong. Oint32_ttem 4 bytes em qualquer computador (aula Inteiros de Tamanho Fixo). Assim, a ficha tem sempre o mesmo tamanho, e o arquivo gravado hoje continua legível amanhã, mesmo com outro compilador. - O preço é guardado em centavos: R$ 15,90 vira o inteiro
1590. Umdoublenão guarda 15,90 exato (aula float e double), e somar preços com vírgula vai acumulando erros. Com centavos inteiros, a conta é exata.
A primeira versão ainda não tem menu. Ela carrega o arquivo; se ele não existe, começa com três produtos. Depois mostra o estoque, "vende" um caderno e salva. Cada vez que roda, o caderno diminui: é o jeito mais simples de ver que o programa lembra.
#include <stdio.h>
#include <stdint.h>
#define ARQUIVO "estoque.bin"
#define MAX_PRODUTOS 100
typedef struct {
int32_t codigo;
char nome[24];
int32_t quantidade;
int32_t preco; // em centavos: 1590 = R$ 15,90
} Produto;
// Lê o estoque do disco e devolve quantos produtos leu.
// Se o arquivo ainda não existe, devolve 0.
int carregar(Produto v[], int max) {
FILE *f = fopen(ARQUIVO, "rb");
if (f == NULL) {
return 0;
}
size_t lidos = fread(v, sizeof(Produto), (size_t)max, f);
fclose(f);
return (int)lidos;
}
// Grava os n produtos no disco. Devolve true se gravou todos.
bool salvar(const Produto v[], int n) {
FILE *f = fopen(ARQUIVO, "wb");
if (f == NULL) {
perror(ARQUIVO);
return false;
}
size_t gravados = fwrite(v, sizeof(Produto), (size_t)n, f);
fclose(f);
return gravados == (size_t)n;
}
int main(void) {
Produto estoque[MAX_PRODUTOS];
int n = carregar(estoque, MAX_PRODUTOS);
if (n == 0) {
printf("Arquivo novo: começando com 3 produtos.\n");
estoque[0] = (Produto){1, "Caderno", 12, 1590};
estoque[1] = (Produto){2, "Caneta", 40, 250};
estoque[2] = (Produto){3, "Borracha", 25, 175};
n = 3;
} else {
printf("Li %d produtos de %s.\n", n, ARQUIVO);
}
for (int i = 0; i < n; i++) {
printf("%d %-10s %3d R$ %d,%02d\n", estoque[i].codigo, estoque[i].nome,
estoque[i].quantidade, estoque[i].preco / 100, estoque[i].preco % 100);
}
estoque[0].quantidade--; // a Ana vendeu um caderno
printf("Vendi 1 %s. Agora são %d.\n", estoque[0].nome, estoque[0].quantidade);
if (!salvar(estoque, n)) {
printf("Erro: o estoque não foi salvo!\n");
return 1;
}
printf("Salvei %d produtos (%zu bytes).\n", n, n * sizeof(Produto));
return 0;
}
| Linha | O que faz |
|---|---|
#include <stdint.h> | Traz o int32_t. |
typedef struct { … } Produto; | A ficha: código, nome (até 23 letras e o '\0'), quantidade e preço em centavos. São 4 + 24 + 4 + 4 = 36 bytes. |
int carregar(Produto v[], int max) | Abre o arquivo com "rb" (ler, binário). Se ele não existe, o fopen devolve NULL e a função devolve 0: estoque vazio, nada de erro. |
fread(v, sizeof(Produto), (size_t)max, f) | "Leia até max fichas de 36 bytes para dentro de v." O arquivo pode ter menos: o fread devolve quantas fichas inteiras leu, e é esse o número de produtos. O (size_t) converte o int para o tipo que o fread espera. |
bool salvar(const Produto v[], int n) | Abre com "wb" (escrever, binário), que cria o arquivo ou apaga o que havia, e grava as n fichas de uma vez. O const promete que salvar não muda o estoque. |
return gravados == (size_t)n; | Verdadeiro só se o fwrite gravou todas as fichas. Com o disco cheio, ele grava menos, e o main avisa. |
estoque[0] = (Produto){1, "Caderno", 12, 1590}; | Preenche uma ficha inteira de uma vez, na ordem dos campos. O (Produto){…} é um "literal composto" C99: uma struct sem nome, criada ali mesmo. |
"R$ %d,%02d", preco / 100, preco % 100 | Mostra os centavos como reais: 1590 / 100 é 15 e 1590 % 100 é 90. O %02d põe o zero da esquerda: 250 centavos viram 2,50, e não 2,5. |
n * sizeof(Produto) | O tamanho do arquivo: 3 × 36 = 108 bytes. O resultado é size_t, por isso o %zu. |
A Ana compilou e rodou três vezes seguidas:
$ gcc -std=c23 -Wall -Wextra estoque.c -o estoque $ ./estoque Arquivo novo: começando com 3 produtos. 1 Caderno 12 R$ 15,90 2 Caneta 40 R$ 2,50 3 Borracha 25 R$ 1,75 Vendi 1 Caderno. Agora são 11. Salvei 3 produtos (108 bytes). $ ./estoque Li 3 produtos de estoque.bin. 1 Caderno 11 R$ 15,90 « lembrou da venda de ontem 2 Caneta 40 R$ 2,50 3 Borracha 25 R$ 1,75 Vendi 1 Caderno. Agora são 10. Salvei 3 produtos (108 bytes). $ ./estoque Li 3 produtos de estoque.bin. 1 Caderno 10 R$ 15,90 2 Caneta 40 R$ 2,50 3 Borracha 25 R$ 1,75 Vendi 1 Caderno. Agora são 9. Salvei 3 produtos (108 bytes). $ ls -l estoque.bin -rw-rw-r-- 1 ana ana 108 out 17 11:29 estoque.bin
O programa terminou três vezes, e a memória dele sumiu três vezes. Quem lembrou do caderno foi o estoque.bin, com os seus 108 bytes. Veja o que tem dentro com o xxd (aula Arquivos Binários):
$ xxd estoque.bin 00000000: 0100 0000 4361 6465 726e 6f00 0000 0000 ....Caderno..... 00000010: 0000 0000 0000 0000 0000 0000 0900 0000 ................ 00000020: 3606 0000 0200 0000 4361 6e65 7461 0000 6.......Caneta.. 00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000040: 2800 0000 fa00 0000 0300 0000 426f 7272 (...........Borr 00000050: 6163 6861 0000 0000 0000 0000 0000 0000 acha............ 00000060: 0000 0000 1900 0000 af00 0000 ............
- Bytes 0 a 3:
01 00 00 00 - O código do caderno, 1. O byte pequeno vem primeiro (little-endian, explicado em Arquivos Binários).
- Bytes 4 a 27:
43 61 64 65 72 6e 6f 00 … - O nome: as letras de "Caderno", o
'\0'e zeros até completar 24 bytes. Um nome curto ocupa a ficha inteira, como num formulário de papel. - Bytes 28 a 31:
09 00 00 00 - A quantidade: 9 cadernos, depois das três vendas.
- Bytes 32 a 35:
36 06 00 00 - O preço:
0x0636é 1590 centavos. - Byte 36 (
0x24) em diante - A ficha da caneta começa exatamente onde a do caderno acaba:
02 00 00 00é o código 2. Cada produto ocupa 36 bytes, um atrás do outro, sem separador.
Como cada ficha tem tamanho fixo, dá para ir direto a um campo. O od abaixo pula 28 bytes (-j 28) e lê 4 (-N 4) como um inteiro (-t d4): é a quantidade de cadernos.
$ od -A d -t d4 -j 28 -N 4 estoque.bin 0000028 9 0000032
Por que int32_t: o editor e o gcc não concordam sobre o long
A primeira ideia da Ana era usar os tipos de sempre: long no código e double no preço. Este programa compara o tamanho das duas fichas:
#include <stdio.h>
#include <stdint.h>
typedef struct { // a ficha com os tipos de sempre
long codigo;
char nome[24];
int quantidade;
double preco;
} ProdutoVelho;
typedef struct { // a ficha do projeto
int32_t codigo;
char nome[24];
int32_t quantidade;
int32_t preco;
} Produto;
int main(void) {
printf("long: %zu bytes, double: %zu bytes\n", sizeof(long), sizeof(double));
printf("ProdutoVelho: %zu bytes\n", sizeof(ProdutoVelho));
printf("Produto: %zu bytes\n", sizeof(Produto));
return 0;
}
$ gcc -std=c23 -Wall -Wextra tamanhos.c -o tamanhos $ ./tamanhos long: 8 bytes, double: 8 bytes ProdutoVelho: 48 bytes Produto: 36 bytes
No editor do guia, o mesmo programa mostra outra coisa:
$ clang -std=c23 -Wall -Wextra tamanhos.c -o tamanhos $ ./tamanhos long: 4 bytes, double: 8 bytes ProdutoVelho: 40 bytes Produto: 36 bytes
O editor é um computador de 32 bits (WebAssembly), em que o long tem 4 bytes; no Linux de 64 bits do laboratório, 8. A ProdutoVelho tem 48 bytes num e 40 no outro (e ainda há bytes de enchimento entre os campos, para alinhar o double; veja Structs). Um arquivo gravado com ela num lado seria lido torto no outro. A Produto, só com int32_t e char, tem 36 bytes nos dois, sem enchimento.
Na pressa, a Ana copiou a linha do fopen de salvar para carregar e deixou o modo "wb" nas duas. Ela guardou essa cópia como errado.c, que compilou sem nenhum aviso. Rodou duas vezes o programa certo e depois o errado:
$ gcc -std=c23 -Wall -Wextra errado.c -o estoque-errado $ rm estoque.bin « começa do zero $ ./estoque Arquivo novo: começando com 3 produtos. 1 Caderno 12 R$ 15,90 2 Caneta 40 R$ 2,50 3 Borracha 25 R$ 1,75 Vendi 1 Caderno. Agora são 11. Salvei 3 produtos (108 bytes). $ ./estoque Li 3 produtos de estoque.bin. 1 Caderno 11 R$ 15,90 2 Caneta 40 R$ 2,50 3 Borracha 25 R$ 1,75 Vendi 1 Caderno. Agora são 10. Salvei 3 produtos (108 bytes). $ ./estoque-errado Arquivo novo: começando com 3 produtos. « os 10 cadernos sumiram 1 Caderno 12 R$ 15,90 2 Caneta 40 R$ 2,50 3 Borracha 25 R$ 1,75 Vendi 1 Caderno. Agora são 11. Salvei 3 produtos (108 bytes).
O que aconteceu: o "w" quer dizer "vou escrever um arquivo novo". O fopen com "wb" apaga o conteúdo na mesma hora, antes de qualquer leitura. Depois, o fread num arquivo aberto só para escrita não lê nada e devolve 0, e o programa achou que era a primeira vez. O estoque de verdade foi perdido sem nenhuma mensagem de erro.
Como consertar: para ler, "rb"; para gravar tudo de novo, "wb". Quando estiver em dúvida, consulte a tabela de modos em Arquivos (fopen e fclose), e guarde uma cópia do arquivo (cp estoque.bin backup.bin) antes de testar um programa novo.
Missão (versão 1): as funções carregar e salvar estão sem o fread e o fwrite. Troque os dois 0 marcados pelas chamadas certas, com sizeof(Produto) como tamanho de cada item. No editor, a pasta começa vazia a cada Rodar; por isso, no fim do main, o programa lê de novo o que acabou de gravar, num array novo, e mostra o que achou no disco.
#include <stdio.h>
#include <stdint.h>
#define ARQUIVO "estoque.bin"
#define MAX_PRODUTOS 100
typedef struct {
int32_t codigo;
char nome[24];
int32_t quantidade;
int32_t preco; // em centavos: 1590 = R$ 15,90
} Produto;
// Lê o estoque do disco e devolve quantos produtos leu.
// Se o arquivo ainda não existe, devolve 0.
int carregar(Produto v[], int max) {
FILE *f = fopen(ARQUIVO, "rb");
if (f == NULL) {
return 0;
}
size_t lidos = 0; // troque o 0 pelo fread
fclose(f);
return (int)lidos;
}
// Grava os n produtos no disco. Devolve true se gravou todos.
bool salvar(const Produto v[], int n) {
FILE *f = fopen(ARQUIVO, "wb");
if (f == NULL) {
perror(ARQUIVO);
return false;
}
size_t gravados = 0; // troque o 0 pelo fwrite
fclose(f);
return gravados == (size_t)n;
}
int main(void) {
Produto estoque[MAX_PRODUTOS];
int n = carregar(estoque, MAX_PRODUTOS);
if (n == 0) {
printf("Arquivo novo: começando com 3 produtos.\n");
estoque[0] = (Produto){1, "Caderno", 12, 1590};
estoque[1] = (Produto){2, "Caneta", 40, 250};
estoque[2] = (Produto){3, "Borracha", 25, 175};
n = 3;
} else {
printf("Li %d produtos de %s.\n", n, ARQUIVO);
}
for (int i = 0; i < n; i++) {
printf("%d %-10s %3d R$ %d,%02d\n", estoque[i].codigo, estoque[i].nome,
estoque[i].quantidade, estoque[i].preco / 100, estoque[i].preco % 100);
}
estoque[0].quantidade--; // a Ana vendeu um caderno
printf("Vendi 1 %s. Agora são %d.\n", estoque[0].nome, estoque[0].quantidade);
if (!salvar(estoque, n)) {
printf("Erro: o estoque não foi salvo!\n");
return 1;
}
printf("Salvei %d produtos (%zu bytes).\n", n, n * sizeof(Produto));
// Só no editor: a pasta começa vazia a cada Rodar. Para ver o arquivo
// funcionando, lemos de novo, num array novo, o que acabamos de gravar.
Produto conferencia[MAX_PRODUTOS];
int lidos = carregar(conferencia, MAX_PRODUTOS);
printf("Conferência: li %d produtos do disco.\n", lidos);
if (lidos > 0) {
printf("No disco, o %s tem %d.\n", conferencia[0].nome, conferencia[0].quantidade);
}
return 0;
}
#include <stdio.h>
#include <stdint.h>
#define ARQUIVO "estoque.bin"
#define MAX_PRODUTOS 100
typedef struct {
int32_t codigo;
char nome[24];
int32_t quantidade;
int32_t preco; // em centavos: 1590 = R$ 15,90
} Produto;
// Lê o estoque do disco e devolve quantos produtos leu.
// Se o arquivo ainda não existe, devolve 0.
int carregar(Produto v[], int max) {
FILE *f = fopen(ARQUIVO, "rb");
if (f == NULL) {
return 0;
}
size_t lidos = fread(v, sizeof(Produto), (size_t)max, f);
fclose(f);
return (int)lidos;
}
// Grava os n produtos no disco. Devolve true se gravou todos.
bool salvar(const Produto v[], int n) {
FILE *f = fopen(ARQUIVO, "wb");
if (f == NULL) {
perror(ARQUIVO);
return false;
}
size_t gravados = fwrite(v, sizeof(Produto), (size_t)n, f);
fclose(f);
return gravados == (size_t)n;
}
int main(void) {
Produto estoque[MAX_PRODUTOS];
int n = carregar(estoque, MAX_PRODUTOS);
if (n == 0) {
printf("Arquivo novo: começando com 3 produtos.\n");
estoque[0] = (Produto){1, "Caderno", 12, 1590};
estoque[1] = (Produto){2, "Caneta", 40, 250};
estoque[2] = (Produto){3, "Borracha", 25, 175};
n = 3;
} else {
printf("Li %d produtos de %s.\n", n, ARQUIVO);
}
for (int i = 0; i < n; i++) {
printf("%d %-10s %3d R$ %d,%02d\n", estoque[i].codigo, estoque[i].nome,
estoque[i].quantidade, estoque[i].preco / 100, estoque[i].preco % 100);
}
estoque[0].quantidade--; // a Ana vendeu um caderno
printf("Vendi 1 %s. Agora são %d.\n", estoque[0].nome, estoque[0].quantidade);
if (!salvar(estoque, n)) {
printf("Erro: o estoque não foi salvo!\n");
return 1;
}
printf("Salvei %d produtos (%zu bytes).\n", n, n * sizeof(Produto));
// Só no editor: a pasta começa vazia a cada Rodar. Para ver o arquivo
// funcionando, lemos de novo, num array novo, o que acabamos de gravar.
Produto conferencia[MAX_PRODUTOS];
int lidos = carregar(conferencia, MAX_PRODUTOS);
printf("Conferência: li %d produtos do disco.\n", lidos);
if (lidos > 0) {
printf("No disco, o %s tem %d.\n", conferencia[0].nome, conferencia[0].quantidade);
}
return 0;
}
Versão 2 · O menu e o cadastro
Vender um caderno a cada execução foi só um teste. Agora o programa vira uma ferramenta: carrega o arquivo, mostra um menu e repete até a Ana escolher 0; aí salva. Os produtos de exemplo saem, e entra o cadastro. Como a Ana vai digitar nomes, quantidades e preços, toda leitura é feita com fgets, uma linha inteira por vez, e depois convertida e conferida, como na Calculadora.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define ARQUIVO "estoque.bin"
#define MAX_PRODUTOS 100
#define ESTOQUE_BAIXO 5
typedef struct {
int32_t codigo;
char nome[24];
int32_t quantidade;
int32_t preco; // em centavos: 1590 = R$ 15,90
} Produto;
// Lê o estoque do disco e devolve quantos produtos leu.
// Se o arquivo ainda não existe, devolve 0.
int carregar(Produto v[], int max) {
FILE *f = fopen(ARQUIVO, "rb");
if (f == NULL) {
return 0;
}
size_t lidos = fread(v, sizeof(Produto), (size_t)max, f);
fclose(f);
return (int)lidos;
}
// Grava os n produtos no disco. Devolve true se gravou todos.
bool salvar(const Produto v[], int n) {
FILE *f = fopen(ARQUIVO, "wb");
if (f == NULL) {
perror(ARQUIVO);
return false;
}
size_t gravados = fwrite(v, sizeof(Produto), (size_t)n, f);
fclose(f);
return gravados == (size_t)n;
}
// Mostra a pergunta e lê uma linha, sem o Enter do fim.
// Devolve false se a entrada acabou (Ctrl+D).
bool ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
return false;
}
linha[strcspn(linha, "\n")] = '\0';
return true;
}
// Lê um número inteiro de 0 a 999999. Devolve -1 se não for válido.
int32_t ler_inteiro(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *fim = NULL;
long valor = strtol(linha, &fim, 10);
if (fim == linha || *fim != '\0' || valor < 0 || valor > 999999) {
printf("\"%s\" não é um número válido.\n", linha);
return -1;
}
return (int32_t)valor;
}
// Lê um preço em reais ("15.90" ou "15,90") e devolve em centavos.
// Devolve -1 se não for válido.
int32_t ler_preco(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *virgula = strchr(linha, ',');
if (virgula != NULL) {
*virgula = '.'; // aceita o jeito brasileiro
}
char *fim = NULL;
double reais = strtod(linha, &fim);
if (fim == linha || *fim != '\0' || reais < 0 || reais > 99999) {
printf("\"%s\" não é um preço válido.\n", linha);
return -1;
}
return (int32_t)(reais * 100 + 0.5); // arredonda para o centavo
}
void listar(const Produto v[], int n) {
if (n == 0) {
printf("O estoque está vazio.\n");
return;
}
long long total = 0;
printf("Cód Produto Qtd Preço\n");
printf("--- ----------------------- ---- ----------\n");
for (int i = 0; i < n; i++) {
printf("%3d %-23s %4d R$ %4d,%02d", v[i].codigo, v[i].nome,
v[i].quantidade, v[i].preco / 100, v[i].preco % 100);
if (v[i].quantidade <= ESTOQUE_BAIXO) {
printf(" (acabando!)");
}
printf("\n");
total += (long long)v[i].quantidade * v[i].preco;
}
printf("Valor do estoque: R$ %lld,%02lld\n", total / 100, total % 100);
}
void cadastrar(Produto v[], int *n) {
if (*n == MAX_PRODUTOS) {
printf("O estoque está cheio (%d produtos).\n", MAX_PRODUTOS);
return;
}
Produto p = {0};
char linha[100];
if (!ler_linha("Nome: ", linha, sizeof linha) || linha[0] == '\0') {
printf("Cadastro cancelado.\n");
return;
}
if (strlen(linha) >= sizeof p.nome) {
printf("Nome comprido demais (no máximo %zu letras).\n", sizeof p.nome - 1);
return;
}
strcpy(p.nome, linha);
p.quantidade = ler_inteiro("Quantidade: ");
if (p.quantidade < 0) {
return;
}
p.preco = ler_preco("Preço (R$): ");
if (p.preco < 0) {
return;
}
p.codigo = 1; // o próximo código livre
for (int i = 0; i < *n; i++) {
if (v[i].codigo >= p.codigo) {
p.codigo = v[i].codigo + 1;
}
}
v[*n] = p;
(*n)++;
printf("Cadastrado: %d - %s\n", p.codigo, p.nome);
}
void mostrar_menu(void) {
printf("1) Listar 2) Cadastrar 0) Sair\n");
}
int main(void) {
Produto estoque[MAX_PRODUTOS];
int n = carregar(estoque, MAX_PRODUTOS);
printf("=== Estoque da Lojinha ===\n");
printf("%d produtos carregados de %s.\n", n, ARQUIVO);
mostrar_menu();
char linha[100];
while (ler_linha("\nOpção: ", linha, sizeof linha)) {
if (strcmp(linha, "1") == 0) {
listar(estoque, n);
} else if (strcmp(linha, "2") == 0) {
cadastrar(estoque, &n);
} else if (strcmp(linha, "0") == 0) {
break;
} else {
printf("Opção desconhecida: %s\n", linha);
mostrar_menu();
}
}
if (!salvar(estoque, n)) {
printf("Erro: o estoque não foi salvo!\n");
return 1;
}
printf("\nSalvei %d produtos em %s. Até amanhã!\n", n, ARQUIVO);
return 0;
}
| Função | O que faz |
|---|---|
ler_linha | Mostra a pergunta, lê a linha com fgets e tira o Enter do fim com strcspn. Devolve false quando a entrada acaba (Ctrl+D), e o while do main para. |
ler_inteiro | Converte a linha com strtol (aula Texto ↔ Número). Recusa se não leu nada (fim == linha), se sobrou texto depois do número (*fim != '\0') ou se o número é negativo ou grande demais. Devolve -1 para dizer "inválido": nenhuma quantidade de verdade é negativa. |
ler_preco | Troca a vírgula por ponto (a Ana digita 15,90, mas o strtod só entende 15.90), converte para double e transforma em centavos, arredondando (veja o cenário abaixo). |
listar | A tabela, com %3d, %-23s e %4d (aula Formatos do printf), o aviso (acabando!) para quem tem 5 ou menos e o valor total. O total é long long e cada conta usa (long long), porque 100 produtos × 1000 unidades × R$ 500,00 passam do maior int32_t. |
cadastrar | Recebe int *n, um ponteiro para o contador do main (aula Passar por Ponteiro), porque precisa aumentá-lo. Confere se cabe, se o nome tem até 23 letras, a quantidade e o preço. O código novo é o maior que já existe mais 1. |
Produto p = {0}; | Começa a ficha toda zerada, inclusive os 24 bytes do nome. Sem isso, os bytes depois do '\0' seriam lixo da memória, e esse lixo iria parar no arquivo. |
strcmp(linha, "1") == 0 | Compara a opção como texto (aula Funções de String): assim, 12 ou 1a não passam por 1. |
Primeiro dia: a Ana cadastra o que tem na loja. No dia seguinte, abre de novo, e os três produtos estão lá; ela cadastra o apontador:
$ rm estoque.bin « o arquivo da versão 1 tinha os exemplos $ gcc -std=c23 -Wall -Wextra estoque.c -o estoque $ ./estoque === Estoque da Lojinha === 0 produtos carregados de estoque.bin. 1) Listar 2) Cadastrar 0) Sair Opção: 1 O estoque está vazio. Opção: 2 Nome: Caderno Quantidade: 12 Preço (R$): 15,90 Cadastrado: 1 - Caderno Opção: 2 Nome: Caneta Quantidade: 40 Preço (R$): 2.50 Cadastrado: 2 - Caneta Opção: 2 Nome: Borracha Quantidade: 3 Preço (R$): 1.75 Cadastrado: 3 - Borracha Opção: 1 Cód Produto Qtd Preço --- ----------------------- ---- ---------- 1 Caderno 12 R$ 15,90 2 Caneta 40 R$ 2,50 3 Borracha 3 R$ 1,75 (acabando!) Valor do estoque: R$ 296,05 Opção: 0 Salvei 3 produtos em estoque.bin. Até amanhã! $ ./estoque === Estoque da Lojinha === 3 produtos carregados de estoque.bin. 1) Listar 2) Cadastrar 0) Sair Opção: 2 Nome: Apontador Quantidade: 8 Preço (R$): 4.35 Cadastrado: 4 - Apontador Opção: 1 Cód Produto Qtd Preço --- ----------------------- ---- ---------- 1 Caderno 12 R$ 15,90 2 Caneta 40 R$ 2,50 3 Borracha 3 R$ 1,75 (acabando!) 4 Apontador 8 R$ 4,35 Valor do estoque: R$ 330,85 Opção: 0 Salvei 4 produtos em estoque.bin. Até amanhã! $ ls -l estoque.bin -rw-rw-r-- 1 ana ana 144 out 17 11:29 estoque.bin « 4 × 36 bytes
E as respostas erradas? Cada uma é recusada com uma mensagem, e o menu continua:
$ ./estoque === Estoque da Lojinha === 4 produtos carregados de estoque.bin. 1) Listar 2) Cadastrar 0) Sair Opção: 2 Nome: Caderno universitario 200 folhas Nome comprido demais (no máximo 23 letras). Opção: 2 Nome: Cola Quantidade: doze "doze" não é um número válido. Opção: 2 Nome: Cola Quantidade: 10 Preço (R$): 3 reais "3 reais" não é um preço válido. Opção: 7 Opção desconhecida: 7 1) Listar 2) Cadastrar 0) Sair Opção: 0 Salvei 4 produtos em estoque.bin. Até amanhã!
strcpy
O strcpy copia até achar o '\0', sem saber o tamanho do destino. Com o nome de 32 letras acima, ele escreveria 33 bytes nos 24 de p.nome e estragaria os campos seguintes (aula Segurança). Conferido o strlen antes, o strcpy é seguro. Os nomes do exemplo não têm acento de propósito: cada letra acentuada ocupa 2 bytes e desalinha a tabela, como no Boletim de Notas.
Na primeira tentativa, a Ana escreveu a conversão para centavos sem o + 0.5: return (int32_t)(reais * 100);. Compilou sem aviso e cadastrou dois produtos:
$ cp estoque.bin bom.bin « uma cópia do estoque, antes do teste $ gcc -std=c23 -Wall -Wextra estoque.c -o estoque $ ./estoque === Estoque da Lojinha === 4 produtos carregados de estoque.bin. 1) Listar 2) Cadastrar 0) Sair Opção: 2 Nome: Cola Quantidade: 6 Preço (R$): 4.35 Cadastrado: 5 - Cola Opção: 2 Nome: Clipes Quantidade: 30 Preço (R$): 0.29 Cadastrado: 6 - Clipes Opção: 1 Cód Produto Qtd Preço --- ----------------------- ---- ---------- 1 Caderno 12 R$ 15,90 2 Caneta 40 R$ 2,50 3 Borracha 3 R$ 1,75 (acabando!) 4 Apontador 8 R$ 4,35 5 Cola 6 R$ 4,34 « digitou 4.35 6 Clipes 30 R$ 0,28 « digitou 0.29 Valor do estoque: R$ 365,29 Opção: 0 Salvei 6 produtos em estoque.bin. Até amanhã! $ cp bom.bin estoque.bin « devolve o estoque de antes do teste
O que aconteceu: o double guarda os números em binário, e 4,35 não tem representação exata: o mais perto que ele consegue é um pouquinho menos, e 4.35 * 100 dá 434.99999999999994. A conversão (int32_t) não arredonda: ela corta a parte depois do ponto, e sobram 434 centavos. Com 0.29, o mesmo: 28.999999999999996 vira 28. O erro acontece só com alguns preços (15.90 e 2.50 deram certo), e por isso é traiçoeiro.
Como consertar: some meio centavo antes de cortar: (int32_t)(reais * 100 + 0.5). 434,99… + 0,5 = 435,49…, cortado dá 435. (Funciona para números positivos, que é o caso de preços. Outra saída é lround, do <math.h>, que no gcc pede -lm; veja Matemática.) É exatamente por isso que o programa guarda centavos: o double aparece uma única vez, na leitura, e é arredondado ali.
Missão (versão 2): este é o programa com o defeito do cenário: a Ana cadastra o apontador a R$ 4,35 e os clipes a R$ 0,29, e a tabela mostra 4,34 e 0,28. Conserte a conversão no fim de ler_preco para os dois preços saírem certos.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define ARQUIVO "estoque.bin"
#define MAX_PRODUTOS 100
#define ESTOQUE_BAIXO 5
typedef struct {
int32_t codigo;
char nome[24];
int32_t quantidade;
int32_t preco; // em centavos: 1590 = R$ 15,90
} Produto;
// Lê o estoque do disco e devolve quantos produtos leu.
// Se o arquivo ainda não existe, devolve 0.
int carregar(Produto v[], int max) {
FILE *f = fopen(ARQUIVO, "rb");
if (f == NULL) {
return 0;
}
size_t lidos = fread(v, sizeof(Produto), (size_t)max, f);
fclose(f);
return (int)lidos;
}
// Grava os n produtos no disco. Devolve true se gravou todos.
bool salvar(const Produto v[], int n) {
FILE *f = fopen(ARQUIVO, "wb");
if (f == NULL) {
perror(ARQUIVO);
return false;
}
size_t gravados = fwrite(v, sizeof(Produto), (size_t)n, f);
fclose(f);
return gravados == (size_t)n;
}
// Mostra a pergunta e lê uma linha, sem o Enter do fim.
// Devolve false se a entrada acabou (Ctrl+D).
bool ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
return false;
}
linha[strcspn(linha, "\n")] = '\0';
return true;
}
// Lê um número inteiro de 0 a 999999. Devolve -1 se não for válido.
int32_t ler_inteiro(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *fim = NULL;
long valor = strtol(linha, &fim, 10);
if (fim == linha || *fim != '\0' || valor < 0 || valor > 999999) {
printf("\"%s\" não é um número válido.\n", linha);
return -1;
}
return (int32_t)valor;
}
// Lê um preço em reais ("15.90" ou "15,90") e devolve em centavos.
// Devolve -1 se não for válido.
int32_t ler_preco(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *virgula = strchr(linha, ',');
if (virgula != NULL) {
*virgula = '.'; // aceita o jeito brasileiro
}
char *fim = NULL;
double reais = strtod(linha, &fim);
if (fim == linha || *fim != '\0' || reais < 0 || reais > 99999) {
printf("\"%s\" não é um preço válido.\n", linha);
return -1;
}
return (int32_t)(reais * 100);
}
void listar(const Produto v[], int n) {
if (n == 0) {
printf("O estoque está vazio.\n");
return;
}
long long total = 0;
printf("Cód Produto Qtd Preço\n");
printf("--- ----------------------- ---- ----------\n");
for (int i = 0; i < n; i++) {
printf("%3d %-23s %4d R$ %4d,%02d", v[i].codigo, v[i].nome,
v[i].quantidade, v[i].preco / 100, v[i].preco % 100);
if (v[i].quantidade <= ESTOQUE_BAIXO) {
printf(" (acabando!)");
}
printf("\n");
total += (long long)v[i].quantidade * v[i].preco;
}
printf("Valor do estoque: R$ %lld,%02lld\n", total / 100, total % 100);
}
void cadastrar(Produto v[], int *n) {
if (*n == MAX_PRODUTOS) {
printf("O estoque está cheio (%d produtos).\n", MAX_PRODUTOS);
return;
}
Produto p = {0};
char linha[100];
if (!ler_linha("Nome: ", linha, sizeof linha) || linha[0] == '\0') {
printf("Cadastro cancelado.\n");
return;
}
if (strlen(linha) >= sizeof p.nome) {
printf("Nome comprido demais (no máximo %zu letras).\n", sizeof p.nome - 1);
return;
}
strcpy(p.nome, linha);
p.quantidade = ler_inteiro("Quantidade: ");
if (p.quantidade < 0) {
return;
}
p.preco = ler_preco("Preço (R$): ");
if (p.preco < 0) {
return;
}
p.codigo = 1; // o próximo código livre
for (int i = 0; i < *n; i++) {
if (v[i].codigo >= p.codigo) {
p.codigo = v[i].codigo + 1;
}
}
v[*n] = p;
(*n)++;
printf("Cadastrado: %d - %s\n", p.codigo, p.nome);
}
void mostrar_menu(void) {
printf("1) Listar 2) Cadastrar 0) Sair\n");
}
int main(void) {
Produto estoque[MAX_PRODUTOS];
int n = carregar(estoque, MAX_PRODUTOS);
printf("=== Estoque da Lojinha ===\n");
printf("%d produtos carregados de %s.\n", n, ARQUIVO);
mostrar_menu();
char linha[100];
while (ler_linha("\nOpção: ", linha, sizeof linha)) {
if (strcmp(linha, "1") == 0) {
listar(estoque, n);
} else if (strcmp(linha, "2") == 0) {
cadastrar(estoque, &n);
} else if (strcmp(linha, "0") == 0) {
break;
} else {
printf("Opção desconhecida: %s\n", linha);
mostrar_menu();
}
}
if (!salvar(estoque, n)) {
printf("Erro: o estoque não foi salvo!\n");
return 1;
}
printf("\nSalvei %d produtos em %s. Até amanhã!\n", n, ARQUIVO);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define ARQUIVO "estoque.bin"
#define MAX_PRODUTOS 100
#define ESTOQUE_BAIXO 5
typedef struct {
int32_t codigo;
char nome[24];
int32_t quantidade;
int32_t preco; // em centavos: 1590 = R$ 15,90
} Produto;
// Lê o estoque do disco e devolve quantos produtos leu.
// Se o arquivo ainda não existe, devolve 0.
int carregar(Produto v[], int max) {
FILE *f = fopen(ARQUIVO, "rb");
if (f == NULL) {
return 0;
}
size_t lidos = fread(v, sizeof(Produto), (size_t)max, f);
fclose(f);
return (int)lidos;
}
// Grava os n produtos no disco. Devolve true se gravou todos.
bool salvar(const Produto v[], int n) {
FILE *f = fopen(ARQUIVO, "wb");
if (f == NULL) {
perror(ARQUIVO);
return false;
}
size_t gravados = fwrite(v, sizeof(Produto), (size_t)n, f);
fclose(f);
return gravados == (size_t)n;
}
// Mostra a pergunta e lê uma linha, sem o Enter do fim.
// Devolve false se a entrada acabou (Ctrl+D).
bool ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
return false;
}
linha[strcspn(linha, "\n")] = '\0';
return true;
}
// Lê um número inteiro de 0 a 999999. Devolve -1 se não for válido.
int32_t ler_inteiro(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *fim = NULL;
long valor = strtol(linha, &fim, 10);
if (fim == linha || *fim != '\0' || valor < 0 || valor > 999999) {
printf("\"%s\" não é um número válido.\n", linha);
return -1;
}
return (int32_t)valor;
}
// Lê um preço em reais ("15.90" ou "15,90") e devolve em centavos.
// Devolve -1 se não for válido.
int32_t ler_preco(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *virgula = strchr(linha, ',');
if (virgula != NULL) {
*virgula = '.'; // aceita o jeito brasileiro
}
char *fim = NULL;
double reais = strtod(linha, &fim);
if (fim == linha || *fim != '\0' || reais < 0 || reais > 99999) {
printf("\"%s\" não é um preço válido.\n", linha);
return -1;
}
return (int32_t)(reais * 100 + 0.5); // arredonda para o centavo
}
void listar(const Produto v[], int n) {
if (n == 0) {
printf("O estoque está vazio.\n");
return;
}
long long total = 0;
printf("Cód Produto Qtd Preço\n");
printf("--- ----------------------- ---- ----------\n");
for (int i = 0; i < n; i++) {
printf("%3d %-23s %4d R$ %4d,%02d", v[i].codigo, v[i].nome,
v[i].quantidade, v[i].preco / 100, v[i].preco % 100);
if (v[i].quantidade <= ESTOQUE_BAIXO) {
printf(" (acabando!)");
}
printf("\n");
total += (long long)v[i].quantidade * v[i].preco;
}
printf("Valor do estoque: R$ %lld,%02lld\n", total / 100, total % 100);
}
void cadastrar(Produto v[], int *n) {
if (*n == MAX_PRODUTOS) {
printf("O estoque está cheio (%d produtos).\n", MAX_PRODUTOS);
return;
}
Produto p = {0};
char linha[100];
if (!ler_linha("Nome: ", linha, sizeof linha) || linha[0] == '\0') {
printf("Cadastro cancelado.\n");
return;
}
if (strlen(linha) >= sizeof p.nome) {
printf("Nome comprido demais (no máximo %zu letras).\n", sizeof p.nome - 1);
return;
}
strcpy(p.nome, linha);
p.quantidade = ler_inteiro("Quantidade: ");
if (p.quantidade < 0) {
return;
}
p.preco = ler_preco("Preço (R$): ");
if (p.preco < 0) {
return;
}
p.codigo = 1; // o próximo código livre
for (int i = 0; i < *n; i++) {
if (v[i].codigo >= p.codigo) {
p.codigo = v[i].codigo + 1;
}
}
v[*n] = p;
(*n)++;
printf("Cadastrado: %d - %s\n", p.codigo, p.nome);
}
void mostrar_menu(void) {
printf("1) Listar 2) Cadastrar 0) Sair\n");
}
int main(void) {
Produto estoque[MAX_PRODUTOS];
int n = carregar(estoque, MAX_PRODUTOS);
printf("=== Estoque da Lojinha ===\n");
printf("%d produtos carregados de %s.\n", n, ARQUIVO);
mostrar_menu();
char linha[100];
while (ler_linha("\nOpção: ", linha, sizeof linha)) {
if (strcmp(linha, "1") == 0) {
listar(estoque, n);
} else if (strcmp(linha, "2") == 0) {
cadastrar(estoque, &n);
} else if (strcmp(linha, "0") == 0) {
break;
} else {
printf("Opção desconhecida: %s\n", linha);
mostrar_menu();
}
}
if (!salvar(estoque, n)) {
printf("Erro: o estoque não foi salvo!\n");
return 1;
}
printf("\nSalvei %d produtos em %s. Até amanhã!\n", n, ARQUIVO);
return 0;
}
Versão 3 · Entrada, saída, salvar e desfazer
Um estoque muda o dia todo: chega mercadoria (entrada) e sai mercadoria (venda). A versão 3 acrescenta quatro opções ao menu:
- 3) Entrada e 4) Saída: a Ana diz o código do produto e quantas unidades; a saída não deixa vender mais do que existe.
- 5) Salvar: grava agora, sem sair.
- 6) Desfazer: joga fora o que mudou desde o último salvamento e carrega de novo o arquivo. É o "voltar" que o caderno de papel não tem, e vem de graça: o arquivo no disco é a última versão salva.
As partes novas (o resto é igual à versão 2):
int achar(const Produto v[], int n, int32_t codigo) {
for (int i = 0; i < n; i++) {
if (v[i].codigo == codigo) {
return i;
}
}
return -1;
}
// sinal = +1 para entrada (chegou mercadoria), -1 para saída (venda)
void movimentar(Produto v[], int n, int sinal) {
int32_t codigo = ler_inteiro("Código: ");
if (codigo < 0) {
return;
}
int i = achar(v, n, codigo);
if (i < 0) {
printf("Não existe produto com o código %d.\n", codigo);
return;
}
int32_t qtd = ler_inteiro("Quantas unidades? ");
if (qtd <= 0) {
return;
}
if (sinal < 0 && qtd > v[i].quantidade) {
printf("Só há %d de %s.\n", v[i].quantidade, v[i].nome);
return;
}
v[i].quantidade += sinal * qtd;
printf("%s: agora são %d.\n", v[i].nome, v[i].quantidade);
}
// dentro do while do main:
} else if (strcmp(linha, "3") == 0) {
movimentar(estoque, n, +1);
} else if (strcmp(linha, "4") == 0) {
movimentar(estoque, n, -1);
} else if (strcmp(linha, "5") == 0) {
if (salvar(estoque, n)) {
printf("Salvei %d produtos em %s.\n", n, ARQUIVO);
} else {
printf("Erro: o estoque não foi salvo!\n");
}
} else if (strcmp(linha, "6") == 0) {
n = carregar(estoque, MAX_PRODUTOS);
printf("Voltei ao que estava salvo: %d produtos.\n", n);
- int achar(const Produto v[], int n, int32_t codigo)
- Procura o código, um produto por vez, e devolve a posição no array, ou
-1se não achou. Com a posição, o resto do programa mexe na ficha certa. - void movimentar(Produto v[], int n, int sinal)
- Uma função só para entrada e saída: o
sinalé+1ou-1, esinal * qtdsoma ou tira. Duas funções quase iguais seriam o dobro de código para manter. - if (qtd <= 0) return;
- O
ler_inteirojá mostrou a mensagem quando a resposta era inválida (-1); mexer em 0 unidades não faz sentido. - if (sinal < 0 && qtd > v[i].quantidade)
- Só na saída: não dá para vender 5 borrachas se há 3. Sem esse teste, o estoque ficaria negativo.
- n = carregar(estoque, MAX_PRODUTOS);
- O desfazer: o array é sobrescrito com o que está no disco, e
nvolta a ser o número de produtos salvos (um produto cadastrado e não salvo também some).
$ gcc -std=c23 -Wall -Wextra estoque.c -o estoque $ ./estoque === Estoque da Lojinha === 4 produtos carregados de estoque.bin. 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Salvar 6) Desfazer 0) Sair Opção: 4 Código: 2 Quantas unidades? 15 Caneta: agora são 25. Opção: 4 Código: 3 Quantas unidades? 5 Só há 3 de Borracha. Opção: 3 Código: 3 Quantas unidades? 20 Borracha: agora são 23. Opção: 4 Código: 9 Não existe produto com o código 9. Opção: 1 Cód Produto Qtd Preço --- ----------------------- ---- ---------- 1 Caderno 12 R$ 15,90 2 Caneta 25 R$ 2,50 3 Borracha 23 R$ 1,75 4 Apontador 8 R$ 4,35 Valor do estoque: R$ 328,35 Opção: 6 Voltei ao que estava salvo: 4 produtos. Opção: 1 Cód Produto Qtd Preço --- ----------------------- ---- ---------- 1 Caderno 12 R$ 15,90 2 Caneta 40 R$ 2,50 « as mudanças foram desfeitas 3 Borracha 3 R$ 1,75 (acabando!) 4 Apontador 8 R$ 4,35 Valor do estoque: R$ 330,85 Opção: 4 Código: 2 Quantas unidades? 15 Caneta: agora são 25. Opção: 3 Código: 3 Quantas unidades? 20 Borracha: agora são 23. Opção: 5 Salvei 4 produtos em estoque.bin. Opção: 0 Salvei 4 produtos em estoque.bin. Até amanhã!
Repare: o programa trabalha o tempo todo com o array na memória, que é rápido, e só encosta no disco para carregar e para salvar. Para um estoque de lojinha, com dezenas ou centenas de produtos, regravar o arquivo inteiro leva um instante. Para arquivos enormes, dá para regravar só uma ficha, com fseek e o modo "r+b" (aula Posição no Arquivo).
Missão (versão 3): a função movimentar está sem o teste da saída, e a Ana consegue vender 5 borrachas quando só tem 3 (o estoque fica em -2). Escreva o teste no lugar do comentário: na saída (sinal < 0), se qtd for maior que a quantidade, mostre Só há 3 de Borracha. (com %d e %s) e saia da função. A entrada da missão cadastra 3 borrachas, tenta vender 5 e depois vende 2.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define ARQUIVO "estoque.bin"
#define MAX_PRODUTOS 100
#define ESTOQUE_BAIXO 5
typedef struct {
int32_t codigo;
char nome[24];
int32_t quantidade;
int32_t preco; // em centavos: 1590 = R$ 15,90
} Produto;
// Lê o estoque do disco e devolve quantos produtos leu.
// Se o arquivo ainda não existe, devolve 0.
int carregar(Produto v[], int max) {
FILE *f = fopen(ARQUIVO, "rb");
if (f == NULL) {
return 0;
}
size_t lidos = fread(v, sizeof(Produto), (size_t)max, f);
fclose(f);
return (int)lidos;
}
// Grava os n produtos no disco. Devolve true se gravou todos.
bool salvar(const Produto v[], int n) {
FILE *f = fopen(ARQUIVO, "wb");
if (f == NULL) {
perror(ARQUIVO);
return false;
}
size_t gravados = fwrite(v, sizeof(Produto), (size_t)n, f);
fclose(f);
return gravados == (size_t)n;
}
// Mostra a pergunta e lê uma linha, sem o Enter do fim.
// Devolve false se a entrada acabou (Ctrl+D).
bool ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
return false;
}
linha[strcspn(linha, "\n")] = '\0';
return true;
}
// Lê um número inteiro de 0 a 999999. Devolve -1 se não for válido.
int32_t ler_inteiro(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *fim = NULL;
long valor = strtol(linha, &fim, 10);
if (fim == linha || *fim != '\0' || valor < 0 || valor > 999999) {
printf("\"%s\" não é um número válido.\n", linha);
return -1;
}
return (int32_t)valor;
}
// Lê um preço em reais ("15.90" ou "15,90") e devolve em centavos.
// Devolve -1 se não for válido.
int32_t ler_preco(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *virgula = strchr(linha, ',');
if (virgula != NULL) {
*virgula = '.'; // aceita o jeito brasileiro
}
char *fim = NULL;
double reais = strtod(linha, &fim);
if (fim == linha || *fim != '\0' || reais < 0 || reais > 99999) {
printf("\"%s\" não é um preço válido.\n", linha);
return -1;
}
return (int32_t)(reais * 100 + 0.5); // arredonda para o centavo
}
int achar(const Produto v[], int n, int32_t codigo) {
for (int i = 0; i < n; i++) {
if (v[i].codigo == codigo) {
return i;
}
}
return -1;
}
void listar(const Produto v[], int n) {
if (n == 0) {
printf("O estoque está vazio.\n");
return;
}
long long total = 0;
printf("Cód Produto Qtd Preço\n");
printf("--- ----------------------- ---- ----------\n");
for (int i = 0; i < n; i++) {
printf("%3d %-23s %4d R$ %4d,%02d", v[i].codigo, v[i].nome,
v[i].quantidade, v[i].preco / 100, v[i].preco % 100);
if (v[i].quantidade <= ESTOQUE_BAIXO) {
printf(" (acabando!)");
}
printf("\n");
total += (long long)v[i].quantidade * v[i].preco;
}
printf("Valor do estoque: R$ %lld,%02lld\n", total / 100, total % 100);
}
void cadastrar(Produto v[], int *n) {
if (*n == MAX_PRODUTOS) {
printf("O estoque está cheio (%d produtos).\n", MAX_PRODUTOS);
return;
}
Produto p = {0};
char linha[100];
if (!ler_linha("Nome: ", linha, sizeof linha) || linha[0] == '\0') {
printf("Cadastro cancelado.\n");
return;
}
if (strlen(linha) >= sizeof p.nome) {
printf("Nome comprido demais (no máximo %zu letras).\n", sizeof p.nome - 1);
return;
}
strcpy(p.nome, linha);
p.quantidade = ler_inteiro("Quantidade: ");
if (p.quantidade < 0) {
return;
}
p.preco = ler_preco("Preço (R$): ");
if (p.preco < 0) {
return;
}
p.codigo = 1; // o próximo código livre
for (int i = 0; i < *n; i++) {
if (v[i].codigo >= p.codigo) {
p.codigo = v[i].codigo + 1;
}
}
v[*n] = p;
(*n)++;
printf("Cadastrado: %d - %s\n", p.codigo, p.nome);
}
// sinal = +1 para entrada (chegou mercadoria), -1 para saída (venda)
void movimentar(Produto v[], int n, int sinal) {
int32_t codigo = ler_inteiro("Código: ");
if (codigo < 0) {
return;
}
int i = achar(v, n, codigo);
if (i < 0) {
printf("Não existe produto com o código %d.\n", codigo);
return;
}
int32_t qtd = ler_inteiro("Quantas unidades? ");
if (qtd <= 0) {
return;
}
// confira aqui se há unidades suficientes para a saída
v[i].quantidade += sinal * qtd;
printf("%s: agora são %d.\n", v[i].nome, v[i].quantidade);
}
void mostrar_menu(void) {
printf("1) Listar 2) Cadastrar 3) Entrada 4) Saída\n");
printf("5) Salvar 6) Desfazer 0) Sair\n");
}
int main(void) {
Produto estoque[MAX_PRODUTOS];
int n = carregar(estoque, MAX_PRODUTOS);
printf("=== Estoque da Lojinha ===\n");
printf("%d produtos carregados de %s.\n", n, ARQUIVO);
mostrar_menu();
char linha[100];
while (ler_linha("\nOpção: ", linha, sizeof linha)) {
if (strcmp(linha, "1") == 0) {
listar(estoque, n);
} else if (strcmp(linha, "2") == 0) {
cadastrar(estoque, &n);
} else if (strcmp(linha, "3") == 0) {
movimentar(estoque, n, +1);
} else if (strcmp(linha, "4") == 0) {
movimentar(estoque, n, -1);
} else if (strcmp(linha, "5") == 0) {
if (salvar(estoque, n)) {
printf("Salvei %d produtos em %s.\n", n, ARQUIVO);
} else {
printf("Erro: o estoque não foi salvo!\n");
}
} else if (strcmp(linha, "6") == 0) {
n = carregar(estoque, MAX_PRODUTOS);
printf("Voltei ao que estava salvo: %d produtos.\n", n);
} else if (strcmp(linha, "0") == 0) {
break;
} else {
printf("Opção desconhecida: %s\n", linha);
mostrar_menu();
}
}
if (!salvar(estoque, n)) {
printf("Erro: o estoque não foi salvo!\n");
return 1;
}
printf("\nSalvei %d produtos em %s. Até amanhã!\n", n, ARQUIVO);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define ARQUIVO "estoque.bin"
#define MAX_PRODUTOS 100
#define ESTOQUE_BAIXO 5
typedef struct {
int32_t codigo;
char nome[24];
int32_t quantidade;
int32_t preco; // em centavos: 1590 = R$ 15,90
} Produto;
// Lê o estoque do disco e devolve quantos produtos leu.
// Se o arquivo ainda não existe, devolve 0.
int carregar(Produto v[], int max) {
FILE *f = fopen(ARQUIVO, "rb");
if (f == NULL) {
return 0;
}
size_t lidos = fread(v, sizeof(Produto), (size_t)max, f);
fclose(f);
return (int)lidos;
}
// Grava os n produtos no disco. Devolve true se gravou todos.
bool salvar(const Produto v[], int n) {
FILE *f = fopen(ARQUIVO, "wb");
if (f == NULL) {
perror(ARQUIVO);
return false;
}
size_t gravados = fwrite(v, sizeof(Produto), (size_t)n, f);
fclose(f);
return gravados == (size_t)n;
}
// Mostra a pergunta e lê uma linha, sem o Enter do fim.
// Devolve false se a entrada acabou (Ctrl+D).
bool ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
return false;
}
linha[strcspn(linha, "\n")] = '\0';
return true;
}
// Lê um número inteiro de 0 a 999999. Devolve -1 se não for válido.
int32_t ler_inteiro(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *fim = NULL;
long valor = strtol(linha, &fim, 10);
if (fim == linha || *fim != '\0' || valor < 0 || valor > 999999) {
printf("\"%s\" não é um número válido.\n", linha);
return -1;
}
return (int32_t)valor;
}
// Lê um preço em reais ("15.90" ou "15,90") e devolve em centavos.
// Devolve -1 se não for válido.
int32_t ler_preco(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *virgula = strchr(linha, ',');
if (virgula != NULL) {
*virgula = '.'; // aceita o jeito brasileiro
}
char *fim = NULL;
double reais = strtod(linha, &fim);
if (fim == linha || *fim != '\0' || reais < 0 || reais > 99999) {
printf("\"%s\" não é um preço válido.\n", linha);
return -1;
}
return (int32_t)(reais * 100 + 0.5); // arredonda para o centavo
}
int achar(const Produto v[], int n, int32_t codigo) {
for (int i = 0; i < n; i++) {
if (v[i].codigo == codigo) {
return i;
}
}
return -1;
}
void listar(const Produto v[], int n) {
if (n == 0) {
printf("O estoque está vazio.\n");
return;
}
long long total = 0;
printf("Cód Produto Qtd Preço\n");
printf("--- ----------------------- ---- ----------\n");
for (int i = 0; i < n; i++) {
printf("%3d %-23s %4d R$ %4d,%02d", v[i].codigo, v[i].nome,
v[i].quantidade, v[i].preco / 100, v[i].preco % 100);
if (v[i].quantidade <= ESTOQUE_BAIXO) {
printf(" (acabando!)");
}
printf("\n");
total += (long long)v[i].quantidade * v[i].preco;
}
printf("Valor do estoque: R$ %lld,%02lld\n", total / 100, total % 100);
}
void cadastrar(Produto v[], int *n) {
if (*n == MAX_PRODUTOS) {
printf("O estoque está cheio (%d produtos).\n", MAX_PRODUTOS);
return;
}
Produto p = {0};
char linha[100];
if (!ler_linha("Nome: ", linha, sizeof linha) || linha[0] == '\0') {
printf("Cadastro cancelado.\n");
return;
}
if (strlen(linha) >= sizeof p.nome) {
printf("Nome comprido demais (no máximo %zu letras).\n", sizeof p.nome - 1);
return;
}
strcpy(p.nome, linha);
p.quantidade = ler_inteiro("Quantidade: ");
if (p.quantidade < 0) {
return;
}
p.preco = ler_preco("Preço (R$): ");
if (p.preco < 0) {
return;
}
p.codigo = 1; // o próximo código livre
for (int i = 0; i < *n; i++) {
if (v[i].codigo >= p.codigo) {
p.codigo = v[i].codigo + 1;
}
}
v[*n] = p;
(*n)++;
printf("Cadastrado: %d - %s\n", p.codigo, p.nome);
}
// sinal = +1 para entrada (chegou mercadoria), -1 para saída (venda)
void movimentar(Produto v[], int n, int sinal) {
int32_t codigo = ler_inteiro("Código: ");
if (codigo < 0) {
return;
}
int i = achar(v, n, codigo);
if (i < 0) {
printf("Não existe produto com o código %d.\n", codigo);
return;
}
int32_t qtd = ler_inteiro("Quantas unidades? ");
if (qtd <= 0) {
return;
}
if (sinal < 0 && qtd > v[i].quantidade) {
printf("Só há %d de %s.\n", v[i].quantidade, v[i].nome);
return;
}
v[i].quantidade += sinal * qtd;
printf("%s: agora são %d.\n", v[i].nome, v[i].quantidade);
}
void mostrar_menu(void) {
printf("1) Listar 2) Cadastrar 3) Entrada 4) Saída\n");
printf("5) Salvar 6) Desfazer 0) Sair\n");
}
int main(void) {
Produto estoque[MAX_PRODUTOS];
int n = carregar(estoque, MAX_PRODUTOS);
printf("=== Estoque da Lojinha ===\n");
printf("%d produtos carregados de %s.\n", n, ARQUIVO);
mostrar_menu();
char linha[100];
while (ler_linha("\nOpção: ", linha, sizeof linha)) {
if (strcmp(linha, "1") == 0) {
listar(estoque, n);
} else if (strcmp(linha, "2") == 0) {
cadastrar(estoque, &n);
} else if (strcmp(linha, "3") == 0) {
movimentar(estoque, n, +1);
} else if (strcmp(linha, "4") == 0) {
movimentar(estoque, n, -1);
} else if (strcmp(linha, "5") == 0) {
if (salvar(estoque, n)) {
printf("Salvei %d produtos em %s.\n", n, ARQUIVO);
} else {
printf("Erro: o estoque não foi salvo!\n");
}
} else if (strcmp(linha, "6") == 0) {
n = carregar(estoque, MAX_PRODUTOS);
printf("Voltei ao que estava salvo: %d produtos.\n", n);
} else if (strcmp(linha, "0") == 0) {
break;
} else {
printf("Opção desconhecida: %s\n", linha);
mostrar_menu();
}
}
if (!salvar(estoque, n)) {
printf("Erro: o estoque não foi salvo!\n");
return 1;
}
printf("\nSalvei %d produtos em %s. Até amanhã!\n", n, ARQUIVO);
return 0;
}
Versão 4 · Um cabeçalho no arquivo e salvar sem perder nada
O programa funciona, mas confia demais no arquivo. Ele lê qualquer estoque.bin que encontrar, de 36 em 36 bytes, sem saber se aquilo é mesmo um estoque. E, se um dia a ficha mudar (um campo novo, por exemplo), os arquivos antigos vão ser lidos tortos, sem nenhum aviso (o cenário "os dois programas não concordam sobre a struct", em Arquivos Binários). A versão 4 resolve isso de dois jeitos.
1. Um cabeçalho com assinatura e versão
Muitos formatos de arquivo começam com alguns bytes fixos, a assinatura (ou "número mágico"): todo PDF começa com %PDF, todo PNG com \x89PNG. O comando file do Linux reconhece o tipo de um arquivo por esses bytes, sem olhar o nome. O estoque da Ana vai começar com 12 bytes de cabeçalho:
| Bytes | Campo | Conteúdo |
|---|---|---|
| 0 a 3 | assinatura | As letras L O J A: "isto é um estoque da lojinha". |
| 4 a 7 | versao | 1. Se o formato mudar, a versão nova grava 2, e cada programa sabe o que está lendo. |
| 8 a 11 | total | Quantos produtos vêm depois. Assim dá para saber se o arquivo foi cortado. |
| 12 em diante | os produtos | As fichas de 36 bytes, como antes. O produto i começa no byte 12 + i × 36. |
2. Salvar num arquivo temporário e trocar no fim
O salvar das versões anteriores abre estoque.bin com "wb", e nesse instante o estoque antigo já foi apagado. Se o programa morrer no meio da gravação (falta de luz, disco cheio, um Ctrl+C), a Ana fica sem o novo e sem o antigo. O jeito seguro, usado por editores de texto e bancos de dados:
- grave tudo num arquivo novo,
estoque.tmp; - confira cada
fwritee ofclose; - só então troque o nome com
rename("estoque.tmp", "estoque.bin"). No Linux, orenamesubstitui o arquivo antigo de uma vez só: quem abrirestoque.binvê o antigo inteiro ou o novo inteiro, nunca um pedaço.
#define TEMPORARIO "estoque.tmp"
#define VERSAO_ARQUIVO 1
typedef struct {
char assinatura[4]; // sempre 'L', 'O', 'J', 'A'
int32_t versao; // o formato do arquivo
int32_t total; // quantos produtos vêm depois
} Cabecalho;
// Lê o estoque do disco. Devolve quantos produtos leu (0 se o arquivo
// ainda não existe) ou -1 se o arquivo não é um estoque da lojinha.
int carregar(Produto v[], int max) {
FILE *f = fopen(ARQUIVO, "rb");
if (f == NULL) {
return 0;
}
Cabecalho cab;
if (fread(&cab, sizeof cab, 1, f) != 1 ||
memcmp(cab.assinatura, "LOJA", 4) != 0) {
printf("%s não é um arquivo da lojinha.\n", ARQUIVO);
fclose(f);
return -1;
}
if (cab.versao != VERSAO_ARQUIVO) {
printf("%s é da versão %d; eu só leio a versão %d.\n",
ARQUIVO, cab.versao, VERSAO_ARQUIVO);
fclose(f);
return -1;
}
if (cab.total < 0 || cab.total > max) {
printf("%s diz ter %d produtos; eu aceito até %d.\n",
ARQUIVO, cab.total, max);
fclose(f);
return -1;
}
size_t lidos = fread(v, sizeof(Produto), (size_t)cab.total, f);
fclose(f);
if (lidos != (size_t)cab.total) {
printf("%s está cortado: li %zu de %d produtos.\n",
ARQUIVO, lidos, cab.total);
return -1;
}
return cab.total;
}
// Grava num arquivo temporário e só no fim troca pelo verdadeiro.
// Devolve true se deu tudo certo.
bool salvar(const Produto v[], int n) {
FILE *f = fopen(TEMPORARIO, "wb");
if (f == NULL) {
perror(TEMPORARIO);
return false;
}
Cabecalho cab = {{'L', 'O', 'J', 'A'}, VERSAO_ARQUIVO, n};
bool ok = fwrite(&cab, sizeof cab, 1, f) == 1 &&
fwrite(v, sizeof(Produto), (size_t)n, f) == (size_t)n;
if (fclose(f) != 0) {
ok = false;
}
if (!ok || rename(TEMPORARIO, ARQUIVO) != 0) {
perror("Erro ao salvar");
remove(TEMPORARIO);
return false;
}
return true;
}
E o main passa a recusar um arquivo estranho, em vez de sobrescrevê-lo ao sair (na opção 6, o desfazer, o mesmo teste: if (n < 0) return 1;):
int main(void) {
Produto estoque[MAX_PRODUTOS];
int n = carregar(estoque, MAX_PRODUTOS);
if (n < 0) {
printf("Não vou mexer nele. Confira o arquivo e tente de novo.\n");
return 1;
}
// ... o resto do main é igual ao da versão 3
}
- typedef struct { char assinatura[4]; int32_t versao; int32_t total; } Cabecalho;
- O cabeçalho também é uma struct, gravada e lida com um
fwritee umfread. São 4 + 4 + 4 = 12 bytes, sem enchimento, no editor e no gcc. A assinatura é um array de 4char, sem'\0': não é uma string, são só 4 bytes. - memcmp(cab.assinatura, "LOJA", 4) != 0
- O
memcmp("memory compare", do<string.h>) compara 4 bytes, como ostrcmp, mas sem procurar'\0'. Devolve 0 se são iguais. - fread(&cab, sizeof cab, 1, f) != 1 || …
- Se o arquivo tem menos de 12 bytes, nem o cabeçalho existe. O
||para no primeiro teste verdadeiro, então omemcmpsó olha o cabeçalho quando ele foi lido. - cab.total < 0 || cab.total > max
- Nunca confie num número que veio do arquivo: um
totalde 5000 faria ofreadescrever além do array de 100 produtos. - lidos != (size_t)cab.total
- O cabeçalho promete um número de produtos; se vieram menos, o arquivo foi cortado.
- Cabecalho cab = {{'L', 'O', 'J', 'A'}, VERSAO_ARQUIVO, n};
- As chaves de dentro preenchem o array da assinatura; depois vêm a versão e o total.
- bool ok = fwrite(…) == 1 && fwrite(…) == (size_t)n;
- As duas gravações têm de dar certo. O
&&nem tenta a segunda se a primeira falhou. - if (fclose(f) != 0)
- O
fwriteguarda os bytes num buffer, e o erro de disco cheio pode aparecer só nofclose. Por isso ele também é conferido. - rename(TEMPORARIO, ARQUIVO)
- Troca o nome; devolve 0 se deu certo. Se algo falhou, o
removeapaga o temporário, e oestoque.binantigo continua intacto.
A primeira coisa que a Ana viu com a versão 4 foi a recusa: o estoque.bin da versão 3 não tem cabeçalho (ele começa com 01 00 00 00, o código do caderno):
$ gcc -std=c23 -Wall -Wextra estoque.c -o estoque $ ./estoque; echo $? estoque.bin não é um arquivo da lojinha. Não vou mexer nele. Confira o arquivo e tente de novo. 1
É a proteção funcionando: sem ela, a versão 4 leria o código do caderno como assinatura e, ao sair, gravaria por cima. Para não perder o estoque, a Ana escreveu um conversor de uso único, que lê as fichas no formato antigo e grava o cabeçalho na frente:
// Transforma o estoque da versão 3 (sem cabeçalho) no formato novo.
#include <stdio.h>
#include <stdint.h>
typedef struct {
int32_t codigo;
char nome[24];
int32_t quantidade;
int32_t preco;
} Produto;
typedef struct {
char assinatura[4];
int32_t versao;
int32_t total;
} Cabecalho;
int main(void) {
Produto v[100];
FILE *f = fopen("estoque-v3.bin", "rb");
if (f == NULL) {
perror("estoque-v3.bin");
return 1;
}
size_t n = fread(v, sizeof(Produto), 100, f);
fclose(f);
Cabecalho cab = {{'L', 'O', 'J', 'A'}, 1, (int32_t)n};
f = fopen("estoque.bin", "wb");
if (f == NULL) {
perror("estoque.bin");
return 1;
}
bool ok = fwrite(&cab, sizeof cab, 1, f) == 1 &&
fwrite(v, sizeof(Produto), n, f) == n;
if (fclose(f) != 0 || !ok) {
printf("Erro ao gravar estoque.bin\n");
return 1;
}
printf("Converti %zu produtos para o formato novo.\n", n);
return 0;
}
$ mv estoque.bin estoque-v3.bin $ gcc -std=c23 -Wall -Wextra converter.c -o converter $ ./converter Converti 4 produtos para o formato novo. $ ls -l estoque.bin estoque-v3.bin -rw-rw-r-- 1 ana ana 156 out 17 11:29 estoque.bin « 12 + 4 × 36 -rw-rw-r-- 1 ana ana 144 out 17 11:29 estoque-v3.bin $ xxd -l 48 estoque.bin 00000000: 4c4f 4a41 0100 0000 0400 0000 0100 0000 LOJA............ 00000010: 4361 6465 726e 6f00 0000 0000 0000 0000 Caderno......... 00000020: 0000 0000 0000 0000 0c00 0000 3606 0000 ............6...
Lá está o cabeçalho: 4c 4f 4a 41 são as letras LOJA, depois a versão (01 00 00 00) e o total (04 00 00 00). O caderno começa no byte 12, com o código 01 00 00 00. Agora a versão 4 abre o estoque normalmente, e a Ana registra a venda de 2 cadernos:
$ ./estoque === Estoque da Lojinha === 4 produtos carregados de estoque.bin. 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Salvar 6) Desfazer 0) Sair Opção: 4 Código: 1 Quantas unidades? 2 Caderno: agora são 10. Opção: 0 Salvei 4 produtos em estoque.bin. Até amanhã!
Depois, testou as proteções, guardando uma cópia antes:
$ cp estoque.bin bom.bin $ echo 'lista de compras' > estoque.bin « um arquivo que não é estoque $ ./estoque; echo $? estoque.bin não é um arquivo da lojinha. Não vou mexer nele. Confira o arquivo e tente de novo. 1 $ cp bom.bin estoque.bin $ truncate -s 60 estoque.bin « corta no meio do 2º produto $ ./estoque estoque.bin está cortado: li 1 de 4 produtos. Não vou mexer nele. Confira o arquivo e tente de novo. $ cp bom.bin estoque.bin $ printf '\x02' | dd of=estoque.bin bs=1 seek=4 conv=notrunc status=none $ xxd -l 12 estoque.bin 00000000: 4c4f 4a41 0200 0000 0400 0000 LOJA........ $ ./estoque estoque.bin é da versão 2; eu só leio a versão 1. Não vou mexer nele. Confira o arquivo e tente de novo. $ cp bom.bin estoque.bin
O truncate -s 60 deixa só os primeiros 60 bytes: o cabeçalho (12), o caderno (36) e um pedaço da caneta. O printf … | dd … seek=4 conv=notrunc troca só o byte 4 por 02, como se o arquivo viesse de uma versão futura do programa. Nos três casos, o programa explicou o problema e saiu sem salvar: o arquivo ficou como estava, para a Ana investigar.
error: incompatible type for argument 1 of ‘fwrite’
Ao escrever o salvar, a Ana esqueceu o & antes do cabeçalho: fwrite(cab, sizeof cab, 1, f).
$ gcc -std=c23 -Wall -Wextra estoque.c -o estoque
estoque.c: In function ‘salvar’:
estoque.c:70:22: error: incompatible type for argument 1 of ‘fwrite’
70 | bool ok = fwrite(cab, sizeof cab, 1, f) == 1 &&
| ^~~
| |
| Cabecalho
In file included from estoque.c:1:
/usr/include/stdio.h:739:46: note: expected ‘const void * restrict’ but argument is of type ‘Cabecalho’
739 | extern size_t fwrite (const void *__restrict __ptr, size_t __size,
| ~~~~~~~~~~~~~~~~~~~~~~~^~~~~
No editor do guia, o Clang diz o mesmo com outras palavras: error: passing 'Cabecalho' to parameter of incompatible type 'const void *'.
O que significa: o primeiro argumento do fwrite é um endereço (const void *, um ponteiro para qualquer coisa; veja Ponteiro void*): "os bytes estão aqui". O cab é a struct inteira, não o endereço dela. A nota mostra a declaração do fwrite no stdio.h e o tipo que ele esperava.
Como consertar: fwrite(&cab, sizeof cab, 1, f). Para um array, como o v dos produtos, o & não vai: o nome do array já é o endereço do primeiro elemento (aula Ponteiros e Arrays).
Missão (versão 4): a aba estoque.bin é um arquivo de texto, uma lista de compras, no lugar do estoque. O carregar lê o cabeçalho mas não confere a assinatura, e a mensagem sai confusa (uma "versão" absurda, que são as letras a de lidas como número). Acrescente o teste da assinatura no if marcado, com memcmp, para o programa dizer estoque.bin não é um arquivo da lojinha. e sair com o código 1.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define ARQUIVO "estoque.bin"
#define TEMPORARIO "estoque.tmp"
#define MAX_PRODUTOS 100
#define ESTOQUE_BAIXO 5
#define VERSAO_ARQUIVO 1
typedef struct {
int32_t codigo;
char nome[24];
int32_t quantidade;
int32_t preco; // em centavos: 1590 = R$ 15,90
} Produto;
typedef struct {
char assinatura[4]; // sempre 'L', 'O', 'J', 'A'
int32_t versao; // o formato do arquivo
int32_t total; // quantos produtos vêm depois
} Cabecalho;
// Lê o estoque do disco. Devolve quantos produtos leu (0 se o arquivo
// ainda não existe) ou -1 se o arquivo não é um estoque da lojinha.
int carregar(Produto v[], int max) {
FILE *f = fopen(ARQUIVO, "rb");
if (f == NULL) {
return 0;
}
Cabecalho cab;
if (fread(&cab, sizeof cab, 1, f) != 1) { // e a assinatura?
printf("%s não é um arquivo da lojinha.\n", ARQUIVO);
fclose(f);
return -1;
}
if (cab.versao != VERSAO_ARQUIVO) {
printf("%s é da versão %d; eu só leio a versão %d.\n",
ARQUIVO, cab.versao, VERSAO_ARQUIVO);
fclose(f);
return -1;
}
if (cab.total < 0 || cab.total > max) {
printf("%s diz ter %d produtos; eu aceito até %d.\n",
ARQUIVO, cab.total, max);
fclose(f);
return -1;
}
size_t lidos = fread(v, sizeof(Produto), (size_t)cab.total, f);
fclose(f);
if (lidos != (size_t)cab.total) {
printf("%s está cortado: li %zu de %d produtos.\n",
ARQUIVO, lidos, cab.total);
return -1;
}
return cab.total;
}
// Grava num arquivo temporário e só no fim troca pelo verdadeiro.
// Devolve true se deu tudo certo.
bool salvar(const Produto v[], int n) {
FILE *f = fopen(TEMPORARIO, "wb");
if (f == NULL) {
perror(TEMPORARIO);
return false;
}
Cabecalho cab = {{'L', 'O', 'J', 'A'}, VERSAO_ARQUIVO, n};
bool ok = fwrite(&cab, sizeof cab, 1, f) == 1 &&
fwrite(v, sizeof(Produto), (size_t)n, f) == (size_t)n;
if (fclose(f) != 0) {
ok = false;
}
if (!ok || rename(TEMPORARIO, ARQUIVO) != 0) {
perror("Erro ao salvar");
remove(TEMPORARIO);
return false;
}
return true;
}
// Mostra a pergunta e lê uma linha, sem o Enter do fim.
// Devolve false se a entrada acabou (Ctrl+D).
bool ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
return false;
}
linha[strcspn(linha, "\n")] = '\0';
return true;
}
// Lê um número inteiro de 0 a 999999. Devolve -1 se não for válido.
int32_t ler_inteiro(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *fim = NULL;
long valor = strtol(linha, &fim, 10);
if (fim == linha || *fim != '\0' || valor < 0 || valor > 999999) {
printf("\"%s\" não é um número válido.\n", linha);
return -1;
}
return (int32_t)valor;
}
// Lê um preço em reais ("15.90" ou "15,90") e devolve em centavos.
// Devolve -1 se não for válido.
int32_t ler_preco(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *virgula = strchr(linha, ',');
if (virgula != NULL) {
*virgula = '.'; // aceita o jeito brasileiro
}
char *fim = NULL;
double reais = strtod(linha, &fim);
if (fim == linha || *fim != '\0' || reais < 0 || reais > 99999) {
printf("\"%s\" não é um preço válido.\n", linha);
return -1;
}
return (int32_t)(reais * 100 + 0.5); // arredonda para o centavo
}
int achar(const Produto v[], int n, int32_t codigo) {
for (int i = 0; i < n; i++) {
if (v[i].codigo == codigo) {
return i;
}
}
return -1;
}
void listar(const Produto v[], int n) {
if (n == 0) {
printf("O estoque está vazio.\n");
return;
}
long long total = 0;
printf("Cód Produto Qtd Preço\n");
printf("--- ----------------------- ---- ----------\n");
for (int i = 0; i < n; i++) {
printf("%3d %-23s %4d R$ %4d,%02d", v[i].codigo, v[i].nome,
v[i].quantidade, v[i].preco / 100, v[i].preco % 100);
if (v[i].quantidade <= ESTOQUE_BAIXO) {
printf(" (acabando!)");
}
printf("\n");
total += (long long)v[i].quantidade * v[i].preco;
}
printf("Valor do estoque: R$ %lld,%02lld\n", total / 100, total % 100);
}
void cadastrar(Produto v[], int *n) {
if (*n == MAX_PRODUTOS) {
printf("O estoque está cheio (%d produtos).\n", MAX_PRODUTOS);
return;
}
Produto p = {0};
char linha[100];
if (!ler_linha("Nome: ", linha, sizeof linha) || linha[0] == '\0') {
printf("Cadastro cancelado.\n");
return;
}
if (strlen(linha) >= sizeof p.nome) {
printf("Nome comprido demais (no máximo %zu letras).\n", sizeof p.nome - 1);
return;
}
strcpy(p.nome, linha);
p.quantidade = ler_inteiro("Quantidade: ");
if (p.quantidade < 0) {
return;
}
p.preco = ler_preco("Preço (R$): ");
if (p.preco < 0) {
return;
}
p.codigo = 1; // o próximo código livre
for (int i = 0; i < *n; i++) {
if (v[i].codigo >= p.codigo) {
p.codigo = v[i].codigo + 1;
}
}
v[*n] = p;
(*n)++;
printf("Cadastrado: %d - %s\n", p.codigo, p.nome);
}
// sinal = +1 para entrada (chegou mercadoria), -1 para saída (venda)
void movimentar(Produto v[], int n, int sinal) {
int32_t codigo = ler_inteiro("Código: ");
if (codigo < 0) {
return;
}
int i = achar(v, n, codigo);
if (i < 0) {
printf("Não existe produto com o código %d.\n", codigo);
return;
}
int32_t qtd = ler_inteiro("Quantas unidades? ");
if (qtd <= 0) {
return;
}
if (sinal < 0 && qtd > v[i].quantidade) {
printf("Só há %d de %s.\n", v[i].quantidade, v[i].nome);
return;
}
v[i].quantidade += sinal * qtd;
printf("%s: agora são %d.\n", v[i].nome, v[i].quantidade);
}
void mostrar_menu(void) {
printf("1) Listar 2) Cadastrar 3) Entrada 4) Saída\n");
printf("5) Salvar 6) Desfazer 0) Sair\n");
}
int main(void) {
Produto estoque[MAX_PRODUTOS];
int n = carregar(estoque, MAX_PRODUTOS);
if (n < 0) {
printf("Não vou mexer nele. Confira o arquivo e tente de novo.\n");
return 1;
}
printf("=== Estoque da Lojinha ===\n");
printf("%d produtos carregados de %s.\n", n, ARQUIVO);
mostrar_menu();
char linha[100];
while (ler_linha("\nOpção: ", linha, sizeof linha)) {
if (strcmp(linha, "1") == 0) {
listar(estoque, n);
} else if (strcmp(linha, "2") == 0) {
cadastrar(estoque, &n);
} else if (strcmp(linha, "3") == 0) {
movimentar(estoque, n, +1);
} else if (strcmp(linha, "4") == 0) {
movimentar(estoque, n, -1);
} else if (strcmp(linha, "5") == 0) {
if (salvar(estoque, n)) {
printf("Salvei %d produtos em %s.\n", n, ARQUIVO);
}
} else if (strcmp(linha, "6") == 0) {
n = carregar(estoque, MAX_PRODUTOS);
if (n < 0) {
return 1;
}
printf("Voltei ao que estava salvo: %d produtos.\n", n);
} else if (strcmp(linha, "0") == 0) {
break;
} else {
printf("Opção desconhecida: %s\n", linha);
mostrar_menu();
}
}
if (!salvar(estoque, n)) {
return 1;
}
printf("\nSalvei %d produtos em %s. Até amanhã!\n", n, ARQUIVO);
return 0;
}
lista de compras
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define ARQUIVO "estoque.bin"
#define TEMPORARIO "estoque.tmp"
#define MAX_PRODUTOS 100
#define ESTOQUE_BAIXO 5
#define VERSAO_ARQUIVO 1
typedef struct {
int32_t codigo;
char nome[24];
int32_t quantidade;
int32_t preco; // em centavos: 1590 = R$ 15,90
} Produto;
typedef struct {
char assinatura[4]; // sempre 'L', 'O', 'J', 'A'
int32_t versao; // o formato do arquivo
int32_t total; // quantos produtos vêm depois
} Cabecalho;
// Lê o estoque do disco. Devolve quantos produtos leu (0 se o arquivo
// ainda não existe) ou -1 se o arquivo não é um estoque da lojinha.
int carregar(Produto v[], int max) {
FILE *f = fopen(ARQUIVO, "rb");
if (f == NULL) {
return 0;
}
Cabecalho cab;
if (fread(&cab, sizeof cab, 1, f) != 1 ||
memcmp(cab.assinatura, "LOJA", 4) != 0) {
printf("%s não é um arquivo da lojinha.\n", ARQUIVO);
fclose(f);
return -1;
}
if (cab.versao != VERSAO_ARQUIVO) {
printf("%s é da versão %d; eu só leio a versão %d.\n",
ARQUIVO, cab.versao, VERSAO_ARQUIVO);
fclose(f);
return -1;
}
if (cab.total < 0 || cab.total > max) {
printf("%s diz ter %d produtos; eu aceito até %d.\n",
ARQUIVO, cab.total, max);
fclose(f);
return -1;
}
size_t lidos = fread(v, sizeof(Produto), (size_t)cab.total, f);
fclose(f);
if (lidos != (size_t)cab.total) {
printf("%s está cortado: li %zu de %d produtos.\n",
ARQUIVO, lidos, cab.total);
return -1;
}
return cab.total;
}
// Grava num arquivo temporário e só no fim troca pelo verdadeiro.
// Devolve true se deu tudo certo.
bool salvar(const Produto v[], int n) {
FILE *f = fopen(TEMPORARIO, "wb");
if (f == NULL) {
perror(TEMPORARIO);
return false;
}
Cabecalho cab = {{'L', 'O', 'J', 'A'}, VERSAO_ARQUIVO, n};
bool ok = fwrite(&cab, sizeof cab, 1, f) == 1 &&
fwrite(v, sizeof(Produto), (size_t)n, f) == (size_t)n;
if (fclose(f) != 0) {
ok = false;
}
if (!ok || rename(TEMPORARIO, ARQUIVO) != 0) {
perror("Erro ao salvar");
remove(TEMPORARIO);
return false;
}
return true;
}
// Mostra a pergunta e lê uma linha, sem o Enter do fim.
// Devolve false se a entrada acabou (Ctrl+D).
bool ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
return false;
}
linha[strcspn(linha, "\n")] = '\0';
return true;
}
// Lê um número inteiro de 0 a 999999. Devolve -1 se não for válido.
int32_t ler_inteiro(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *fim = NULL;
long valor = strtol(linha, &fim, 10);
if (fim == linha || *fim != '\0' || valor < 0 || valor > 999999) {
printf("\"%s\" não é um número válido.\n", linha);
return -1;
}
return (int32_t)valor;
}
// Lê um preço em reais ("15.90" ou "15,90") e devolve em centavos.
// Devolve -1 se não for válido.
int32_t ler_preco(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *virgula = strchr(linha, ',');
if (virgula != NULL) {
*virgula = '.'; // aceita o jeito brasileiro
}
char *fim = NULL;
double reais = strtod(linha, &fim);
if (fim == linha || *fim != '\0' || reais < 0 || reais > 99999) {
printf("\"%s\" não é um preço válido.\n", linha);
return -1;
}
return (int32_t)(reais * 100 + 0.5); // arredonda para o centavo
}
int achar(const Produto v[], int n, int32_t codigo) {
for (int i = 0; i < n; i++) {
if (v[i].codigo == codigo) {
return i;
}
}
return -1;
}
void listar(const Produto v[], int n) {
if (n == 0) {
printf("O estoque está vazio.\n");
return;
}
long long total = 0;
printf("Cód Produto Qtd Preço\n");
printf("--- ----------------------- ---- ----------\n");
for (int i = 0; i < n; i++) {
printf("%3d %-23s %4d R$ %4d,%02d", v[i].codigo, v[i].nome,
v[i].quantidade, v[i].preco / 100, v[i].preco % 100);
if (v[i].quantidade <= ESTOQUE_BAIXO) {
printf(" (acabando!)");
}
printf("\n");
total += (long long)v[i].quantidade * v[i].preco;
}
printf("Valor do estoque: R$ %lld,%02lld\n", total / 100, total % 100);
}
void cadastrar(Produto v[], int *n) {
if (*n == MAX_PRODUTOS) {
printf("O estoque está cheio (%d produtos).\n", MAX_PRODUTOS);
return;
}
Produto p = {0};
char linha[100];
if (!ler_linha("Nome: ", linha, sizeof linha) || linha[0] == '\0') {
printf("Cadastro cancelado.\n");
return;
}
if (strlen(linha) >= sizeof p.nome) {
printf("Nome comprido demais (no máximo %zu letras).\n", sizeof p.nome - 1);
return;
}
strcpy(p.nome, linha);
p.quantidade = ler_inteiro("Quantidade: ");
if (p.quantidade < 0) {
return;
}
p.preco = ler_preco("Preço (R$): ");
if (p.preco < 0) {
return;
}
p.codigo = 1; // o próximo código livre
for (int i = 0; i < *n; i++) {
if (v[i].codigo >= p.codigo) {
p.codigo = v[i].codigo + 1;
}
}
v[*n] = p;
(*n)++;
printf("Cadastrado: %d - %s\n", p.codigo, p.nome);
}
// sinal = +1 para entrada (chegou mercadoria), -1 para saída (venda)
void movimentar(Produto v[], int n, int sinal) {
int32_t codigo = ler_inteiro("Código: ");
if (codigo < 0) {
return;
}
int i = achar(v, n, codigo);
if (i < 0) {
printf("Não existe produto com o código %d.\n", codigo);
return;
}
int32_t qtd = ler_inteiro("Quantas unidades? ");
if (qtd <= 0) {
return;
}
if (sinal < 0 && qtd > v[i].quantidade) {
printf("Só há %d de %s.\n", v[i].quantidade, v[i].nome);
return;
}
v[i].quantidade += sinal * qtd;
printf("%s: agora são %d.\n", v[i].nome, v[i].quantidade);
}
void mostrar_menu(void) {
printf("1) Listar 2) Cadastrar 3) Entrada 4) Saída\n");
printf("5) Salvar 6) Desfazer 0) Sair\n");
}
int main(void) {
Produto estoque[MAX_PRODUTOS];
int n = carregar(estoque, MAX_PRODUTOS);
if (n < 0) {
printf("Não vou mexer nele. Confira o arquivo e tente de novo.\n");
return 1;
}
printf("=== Estoque da Lojinha ===\n");
printf("%d produtos carregados de %s.\n", n, ARQUIVO);
mostrar_menu();
char linha[100];
while (ler_linha("\nOpção: ", linha, sizeof linha)) {
if (strcmp(linha, "1") == 0) {
listar(estoque, n);
} else if (strcmp(linha, "2") == 0) {
cadastrar(estoque, &n);
} else if (strcmp(linha, "3") == 0) {
movimentar(estoque, n, +1);
} else if (strcmp(linha, "4") == 0) {
movimentar(estoque, n, -1);
} else if (strcmp(linha, "5") == 0) {
if (salvar(estoque, n)) {
printf("Salvei %d produtos em %s.\n", n, ARQUIVO);
}
} else if (strcmp(linha, "6") == 0) {
n = carregar(estoque, MAX_PRODUTOS);
if (n < 0) {
return 1;
}
printf("Voltei ao que estava salvo: %d produtos.\n", n);
} else if (strcmp(linha, "0") == 0) {
break;
} else {
printf("Opção desconhecida: %s\n", linha);
mostrar_menu();
}
}
if (!salvar(estoque, n)) {
return 1;
}
printf("\nSalvei %d produtos em %s. Até amanhã!\n", n, ARQUIVO);
return 0;
}
O programa completo
A versão final, igual à versão 4. Compile com gcc -std=c23 -Wall -Wextra estoque.c -o estoque:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define ARQUIVO "estoque.bin"
#define TEMPORARIO "estoque.tmp"
#define MAX_PRODUTOS 100
#define ESTOQUE_BAIXO 5
#define VERSAO_ARQUIVO 1
typedef struct {
int32_t codigo;
char nome[24];
int32_t quantidade;
int32_t preco; // em centavos: 1590 = R$ 15,90
} Produto;
typedef struct {
char assinatura[4]; // sempre 'L', 'O', 'J', 'A'
int32_t versao; // o formato do arquivo
int32_t total; // quantos produtos vêm depois
} Cabecalho;
// Lê o estoque do disco. Devolve quantos produtos leu (0 se o arquivo
// ainda não existe) ou -1 se o arquivo não é um estoque da lojinha.
int carregar(Produto v[], int max) {
FILE *f = fopen(ARQUIVO, "rb");
if (f == NULL) {
return 0;
}
Cabecalho cab;
if (fread(&cab, sizeof cab, 1, f) != 1 ||
memcmp(cab.assinatura, "LOJA", 4) != 0) {
printf("%s não é um arquivo da lojinha.\n", ARQUIVO);
fclose(f);
return -1;
}
if (cab.versao != VERSAO_ARQUIVO) {
printf("%s é da versão %d; eu só leio a versão %d.\n",
ARQUIVO, cab.versao, VERSAO_ARQUIVO);
fclose(f);
return -1;
}
if (cab.total < 0 || cab.total > max) {
printf("%s diz ter %d produtos; eu aceito até %d.\n",
ARQUIVO, cab.total, max);
fclose(f);
return -1;
}
size_t lidos = fread(v, sizeof(Produto), (size_t)cab.total, f);
fclose(f);
if (lidos != (size_t)cab.total) {
printf("%s está cortado: li %zu de %d produtos.\n",
ARQUIVO, lidos, cab.total);
return -1;
}
return cab.total;
}
// Grava num arquivo temporário e só no fim troca pelo verdadeiro.
// Devolve true se deu tudo certo.
bool salvar(const Produto v[], int n) {
FILE *f = fopen(TEMPORARIO, "wb");
if (f == NULL) {
perror(TEMPORARIO);
return false;
}
Cabecalho cab = {{'L', 'O', 'J', 'A'}, VERSAO_ARQUIVO, n};
bool ok = fwrite(&cab, sizeof cab, 1, f) == 1 &&
fwrite(v, sizeof(Produto), (size_t)n, f) == (size_t)n;
if (fclose(f) != 0) {
ok = false;
}
if (!ok || rename(TEMPORARIO, ARQUIVO) != 0) {
perror("Erro ao salvar");
remove(TEMPORARIO);
return false;
}
return true;
}
// Mostra a pergunta e lê uma linha, sem o Enter do fim.
// Devolve false se a entrada acabou (Ctrl+D).
bool ler_linha(const char *pergunta, char *linha, int tamanho) {
printf("%s", pergunta);
if (fgets(linha, tamanho, stdin) == NULL) {
return false;
}
linha[strcspn(linha, "\n")] = '\0';
return true;
}
// Lê um número inteiro de 0 a 999999. Devolve -1 se não for válido.
int32_t ler_inteiro(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *fim = NULL;
long valor = strtol(linha, &fim, 10);
if (fim == linha || *fim != '\0' || valor < 0 || valor > 999999) {
printf("\"%s\" não é um número válido.\n", linha);
return -1;
}
return (int32_t)valor;
}
// Lê um preço em reais ("15.90" ou "15,90") e devolve em centavos.
// Devolve -1 se não for válido.
int32_t ler_preco(const char *pergunta) {
char linha[100];
if (!ler_linha(pergunta, linha, sizeof linha)) {
return -1;
}
char *virgula = strchr(linha, ',');
if (virgula != NULL) {
*virgula = '.'; // aceita o jeito brasileiro
}
char *fim = NULL;
double reais = strtod(linha, &fim);
if (fim == linha || *fim != '\0' || reais < 0 || reais > 99999) {
printf("\"%s\" não é um preço válido.\n", linha);
return -1;
}
return (int32_t)(reais * 100 + 0.5); // arredonda para o centavo
}
int achar(const Produto v[], int n, int32_t codigo) {
for (int i = 0; i < n; i++) {
if (v[i].codigo == codigo) {
return i;
}
}
return -1;
}
void listar(const Produto v[], int n) {
if (n == 0) {
printf("O estoque está vazio.\n");
return;
}
long long total = 0;
printf("Cód Produto Qtd Preço\n");
printf("--- ----------------------- ---- ----------\n");
for (int i = 0; i < n; i++) {
printf("%3d %-23s %4d R$ %4d,%02d", v[i].codigo, v[i].nome,
v[i].quantidade, v[i].preco / 100, v[i].preco % 100);
if (v[i].quantidade <= ESTOQUE_BAIXO) {
printf(" (acabando!)");
}
printf("\n");
total += (long long)v[i].quantidade * v[i].preco;
}
printf("Valor do estoque: R$ %lld,%02lld\n", total / 100, total % 100);
}
void cadastrar(Produto v[], int *n) {
if (*n == MAX_PRODUTOS) {
printf("O estoque está cheio (%d produtos).\n", MAX_PRODUTOS);
return;
}
Produto p = {0};
char linha[100];
if (!ler_linha("Nome: ", linha, sizeof linha) || linha[0] == '\0') {
printf("Cadastro cancelado.\n");
return;
}
if (strlen(linha) >= sizeof p.nome) {
printf("Nome comprido demais (no máximo %zu letras).\n", sizeof p.nome - 1);
return;
}
strcpy(p.nome, linha);
p.quantidade = ler_inteiro("Quantidade: ");
if (p.quantidade < 0) {
return;
}
p.preco = ler_preco("Preço (R$): ");
if (p.preco < 0) {
return;
}
p.codigo = 1; // o próximo código livre
for (int i = 0; i < *n; i++) {
if (v[i].codigo >= p.codigo) {
p.codigo = v[i].codigo + 1;
}
}
v[*n] = p;
(*n)++;
printf("Cadastrado: %d - %s\n", p.codigo, p.nome);
}
// sinal = +1 para entrada (chegou mercadoria), -1 para saída (venda)
void movimentar(Produto v[], int n, int sinal) {
int32_t codigo = ler_inteiro("Código: ");
if (codigo < 0) {
return;
}
int i = achar(v, n, codigo);
if (i < 0) {
printf("Não existe produto com o código %d.\n", codigo);
return;
}
int32_t qtd = ler_inteiro("Quantas unidades? ");
if (qtd <= 0) {
return;
}
if (sinal < 0 && qtd > v[i].quantidade) {
printf("Só há %d de %s.\n", v[i].quantidade, v[i].nome);
return;
}
v[i].quantidade += sinal * qtd;
printf("%s: agora são %d.\n", v[i].nome, v[i].quantidade);
}
void mostrar_menu(void) {
printf("1) Listar 2) Cadastrar 3) Entrada 4) Saída\n");
printf("5) Salvar 6) Desfazer 0) Sair\n");
}
int main(void) {
Produto estoque[MAX_PRODUTOS];
int n = carregar(estoque, MAX_PRODUTOS);
if (n < 0) {
printf("Não vou mexer nele. Confira o arquivo e tente de novo.\n");
return 1;
}
printf("=== Estoque da Lojinha ===\n");
printf("%d produtos carregados de %s.\n", n, ARQUIVO);
mostrar_menu();
char linha[100];
while (ler_linha("\nOpção: ", linha, sizeof linha)) {
if (strcmp(linha, "1") == 0) {
listar(estoque, n);
} else if (strcmp(linha, "2") == 0) {
cadastrar(estoque, &n);
} else if (strcmp(linha, "3") == 0) {
movimentar(estoque, n, +1);
} else if (strcmp(linha, "4") == 0) {
movimentar(estoque, n, -1);
} else if (strcmp(linha, "5") == 0) {
if (salvar(estoque, n)) {
printf("Salvei %d produtos em %s.\n", n, ARQUIVO);
}
} else if (strcmp(linha, "6") == 0) {
n = carregar(estoque, MAX_PRODUTOS);
if (n < 0) {
return 1;
}
printf("Voltei ao que estava salvo: %d produtos.\n", n);
} else if (strcmp(linha, "0") == 0) {
break;
} else {
printf("Opção desconhecida: %s\n", linha);
mostrar_menu();
}
}
if (!salvar(estoque, n)) {
return 1;
}
printf("\nSalvei %d produtos em %s. Até amanhã!\n", n, ARQUIVO);
return 0;
}
O que o projeto juntou: structs e typedef, int32_t, fwrite e fread, fgets, strtol e strtod, passagem por ponteiro, strcmp, strcpy e memcmp e o perror.
Rodando no laboratório
Depois da sessão do começo da página, a Ana abriu o programa de novo: a cola cadastrada e as 5 canetas vendidas estão lá. E o estoque.tmp não sobrou na pasta, porque o rename o transformou no estoque.bin:
$ ./estoque === Estoque da Lojinha === 5 produtos carregados de estoque.bin. 1) Listar 2) Cadastrar 3) Entrada 4) Saída 5) Salvar 6) Desfazer 0) Sair Opção: 1 Cód Produto Qtd Preço --- ----------------------- ---- ---------- 1 Caderno 10 R$ 15,90 2 Caneta 20 R$ 2,50 3 Borracha 23 R$ 1,75 4 Apontador 8 R$ 4,35 5 Cola 10 R$ 3,50 Valor do estoque: R$ 319,05 Opção: 0 Salvei 5 produtos em estoque.bin. Até amanhã! $ ls estoque* estoque estoque.bin estoque.c estoque-errado estoque-v3.bin
Desafios extras
- Remover um produto. Acrescente a opção
7) Remover: ache a posição comachare puxe os produtos seguintes uma casa para trás (v[j] = v[j + 1]), diminuindon. - Buscar pelo nome. Mostre só os produtos cujo nome contém o texto digitado, com
strstr(aula Funções de String). - Lista de compras. Uma opção que mostra só os produtos
(acabando!)e quanto custaria repor cada um até 20 unidades. - Em ordem de nome. Liste em ordem alfabética com
qsorte uma função que compara comstrcmp(aula Ordenação). - Exportar para planilha. Grave um
estoque.csvcomfprintf, uma linha por produto (1;Caderno;10;15,90), para abrir no LibreOffice (aula Escrever Arquivos). - Versão 2 do formato. Acrescente à ficha um campo
int32_t minimo(o estoque mínimo de cada produto, no lugar do 5 fixo). Grave comVERSAO_ARQUIVO 2e faça ocarregaraceitar também a versão 1, completando ominimocom 5.
Exercícios
1. Por que o preço é guardado como int32_t em centavos, e não como double em reais?
2. Uma struct com long codigo tem 48 bytes no gcc do laboratório e 40 no editor do guia. O que isso causa num arquivo binário?
3. O que acontece com um estoque.bin de 144 bytes quando o programa faz fopen("estoque.bin", "wb")?
4. Por que a versão 4 grava em estoque.tmp e só depois faz rename para estoque.bin?
5. No arquivo da versão 4, o cabeçalho tem 12 bytes e cada produto 36. Em que byte começa o terceiro produto (o de índice 2)?
6. Complete a conversão de reais para centavos, sem perder o centavo do 4,35:
7. Complete a gravação do cabeçalho cab (uma variável, não um array):
Resumo
- Uma struct de tamanho fixo vira um registro no arquivo:
fwrite(v, sizeof(Produto), n, f)grava o array inteiro efreadlê de volta. - Para o disco, use
int32_te companhia: olongtem 8 bytes no gcc e 4 no editor, e a ficha mudaria de tamanho. - Dinheiro em centavos inteiros. Ao converter de
double, arredonde com+ 0.5: o(int32_t)corta, e 4,35 viraria 4,34. "rb"para ler,"wb"para regravar tudo; o"wb"apaga o arquivo na hora.- Confira o que
fread,fwriteefclosedevolvem, e nunca confie num número que veio do arquivo. - Um cabeçalho com assinatura e versão faz o programa recusar arquivos estranhos, cortados ou de outro formato.
- Salvar com segurança: grave num temporário, confira tudo e troque com
rename.
Próximo projeto: Contador de Palavras.