Projeto Final: Sistema de Gerenciamento de Biblioteca
Construir uma casa não é apenas aprender a assentar tijolos — agora é hora de combinar todas as suas habilidades e construir um prédio completo, dos alicerces ao telhado.
Análise de Requisitos
Vamos construir um sistema de gerenciamento de biblioteca em linha de comando com as seguintes funcionalidades:
- Adicionar livros (título, autor, ISBN, preço)
- Remover livros (por ISBN)
- Atualizar informações do livro
- Buscar livros (por título ou ISBN)
- Listar todos os livros
- Salvar dados em arquivo (persistência)
- Carregar dados do arquivo
Design de Estrutura de Dados
As informações do livro são representadas por uma struct, e todos os livros são gerenciados com um array dinâmico:
typedef struct {
char isbn[14];
char title[128];
char author[64];
double price;
} Book;
typedef struct {
Book *books;
int count;
int capacity;
} Library;
Library gerencia um array dinâmico: books é o ponteiro de dados, count é o número atual de itens e capacity é a capacidade alocada. Quando count atinge capacity, ele se expande automaticamente.
Organização de Arquivos do Projeto
bookmanager/
├── Makefile
├── main.c
├── library.h
├── library.c
├── storage.h
└── storage.c
library.h/library.c: lógica central de gerenciamento de livros (CRUD)storage.h/storage.c: E/S de arquivomain.c: ponto de entrada do programa principal e interface com o usuárioMakefile: script de build
library.h
#ifndef LIBRARY_H
#define LIBRARY_H
typedef struct {
char isbn[14];
char title[128];
char author[64];
double price;
} Book;
typedef struct {
Book *books;
int count;
int capacity;
} Library;
void library_init(Library *lib);
void library_free(Library *lib);
int library_add(Library *lib, const Book *book);
int library_remove(Library *lib, const char *isbn);
Book *library_find_by_isbn(Library *lib, const char *isbn);
void library_find_by_title(Library *lib, const char *keyword,
Book **results, int *result_count);
int library_update(Library *lib, const char *isbn, const Book *new_info);
void library_list(const Library *lib);
#endif
library.c
#include "library.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int ensure_capacity(Library *lib) {
if (lib->count < lib->capacity) {
return 1;
}
int new_cap = lib->capacity == 0 ? 4 : lib->capacity * 2;
Book *new_books = realloc(lib->books, new_cap * sizeof(Book));
if (new_books == NULL) {
return 0;
}
lib->books = new_books;
lib->capacity = new_cap;
return 1;
}
void library_init(Library *lib) {
lib->books = NULL;
lib->count = 0;
lib->capacity = 0;
}
void library_free(Library *lib) {
free(lib->books);
lib->books = NULL;
lib->count = 0;
lib->capacity = 0;
}
int library_add(Library *lib, const Book *book) {
if (!ensure_capacity(lib)) {
return 0;
}
lib->books[lib->count] = *book;
lib->count++;
return 1;
}
int library_remove(Library *lib, const char *isbn) {
for (int i = 0; i < lib->count; i++) {
if (strcmp(lib->books[i].isbn, isbn) == 0) {
for (int j = i; j < lib->count - 1; j++) {
lib->books[j] = lib->books[j + 1];
}
lib->count--;
return 1;
}
}
return 0;
}
Book *library_find_by_isbn(Library *lib, const char *isbn) {
for (int i = 0; i < lib->count; i++) {
if (strcmp(lib->books[i].isbn, isbn) == 0) {
return &lib->books[i];
}
}
return NULL;
}
void library_find_by_title(Library *lib, const char *keyword,
Book **results, int *result_count) {
*result_count = 0;
for (int i = 0; i < lib->count; i++) {
if (strstr(lib->books[i].title, keyword) != NULL) {
results[*result_count] = &lib->books[i];
(*result_count)++;
}
}
}
int library_update(Library *lib, const char *isbn, const Book *new_info) {
Book *existing = library_find_by_isbn(lib, isbn);
if (existing == NULL) {
return 0;
}
*existing = *new_info;
return 1;
}
void library_list(const Library *lib) {
if (lib->count == 0) {
printf("Nenhum livro na biblioteca.\n");
return;
}
printf("%-14s %-30s %-20s %-8s\n", "ISBN", "Titulo", "Autor", "Preco");
printf("--------------------------------------------------------------\n");
for (int i = 0; i < lib->count; i++) {
printf("%-14s %-30s %-20s %-8.2f\n",
lib->books[i].isbn,
lib->books[i].title,
lib->books[i].author,
lib->books[i].price);
}
printf("Total: %d livros\n", lib->count);
}
ensure_capacity é uma função interna, decorada com static para limitar seu escopo a este arquivo. A estratégia de dobrar o array dinâmico é prática padrão, garantindo eficiência de inserção amortizada O(1).
storage.h
#ifndef STORAGE_H
#define STORAGE_H
#include "library.h"
int storage_save(const Library *lib, const char *filename);
int storage_load(Library *lib, const char *filename);
#endif
storage.c
#include "storage.h"
#include <stdio.h>
#include <string.h>
int storage_save(const Library *lib, const char *filename) {
FILE *fp = fopen(filename, "w");
if (fp == NULL) {
return 0;
}
for (int i = 0; i < lib->count; i++) {
fprintf(fp, "%s|%s|%s|%.2f\n",
lib->books[i].isbn,
lib->books[i].title,
lib->books[i].author,
lib->books[i].price);
}
fclose(fp);
return 1;
}
int storage_load(Library *lib, const char *filename) {
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
return 1;
}
Book book;
while (fscanf(fp, "%13[^|]|%127[^|]|%63[^|]|%lf\n",
book.isbn, book.title, book.author, &book.price) == 4) {
if (!library_add(lib, &book)) {
fclose(fp);
return 0;
}
}
fclose(fp);
return 1;
}
%[^|] do fscanf lê uma string até o delimitador |. %13[^|] limita a leitura a no máximo 13 caracteres, prevenindo estouro. Essa é a forma segura de usar E/S formatada.
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "library.h"
#include "storage.h"
#define DATA_FILE "library.dat"
static void input_string(const char *prompt, char *buf, int size) {
printf("%s", prompt);
fflush(stdout);
if (fgets(buf, size, stdin) == NULL) {
buf[0] = '\0';
return;
}
buf[strcspn(buf, "\n")] = '\0';
}
static void cmd_add(Library *lib) {
Book book;
input_string("Digite ISBN: ", book.isbn, sizeof(book.isbn));
input_string("Digite titulo: ", book.title, sizeof(book.title));
input_string("Digite autor: ", book.author, sizeof(book.author));
char price_str[32];
input_string("Digite preco: ", price_str, sizeof(price_str));
book.price = atof(price_str);
if (library_find_by_isbn(lib, book.isbn) != NULL) {
printf("Erro: ISBN %s ja existe\n", book.isbn);
return;
}
if (library_add(lib, &book)) {
printf("Adicionado com sucesso!\n");
} else {
printf("Falha ao adicionar: sem memoria\n");
}
}
static void cmd_remove(Library *lib) {
char isbn[14];
input_string("Digite ISBN para remover: ", isbn, sizeof(isbn));
if (library_remove(lib, isbn)) {
printf("Removido com sucesso!\n");
} else {
printf("Livro com esse ISBN nao encontrado\n");
}
}
static void cmd_find(Library *lib) {
printf("1. Buscar por ISBN 2. Buscar por titulo\n");
char choice[8];
input_string("Escolha: ", choice, sizeof(choice));
if (strcmp(choice, "1") == 0) {
char isbn[14];
input_string("Digite ISBN: ", isbn, sizeof(isbn));
Book *book = library_find_by_isbn(lib, isbn);
if (book != NULL) {
printf("ISBN: %s\n", book->isbn);
printf("Titulo: %s\n", book->title);
printf("Autor: %s\n", book->author);
printf("Preco: %.2f\n", book->price);
} else {
printf("Nao encontrado\n");
}
} else if (strcmp(choice, "2") == 0) {
char keyword[128];
input_string("Digite palavra-chave: ", keyword, sizeof(keyword));
Book *results[100];
int result_count = 0;
library_find_by_title(lib, keyword, results, &result_count);
if (result_count == 0) {
printf("Nenhum livro contendo \"%s\" encontrado\n", keyword);
} else {
for (int i = 0; i < result_count; i++) {
printf("%-14s %-30s %.2f\n",
results[i]->isbn, results[i]->title, results[i]->price);
}
printf("Encontrados %d livros\n", result_count);
}
}
}
static void cmd_update(Library *lib) {
char isbn[14];
input_string("Digite ISBN para atualizar: ", isbn, sizeof(isbn));
Book *existing = library_find_by_isbn(lib, isbn);
if (existing == NULL) {
printf("Livro com esse ISBN nao encontrado\n");
return;
}
Book new_info = *existing;
printf("Titulo atual: %s (pressione Enter para manter)\n", existing->title);
input_string("Novo titulo: ", new_info.title, sizeof(new_info.title));
if (new_info.title[0] == '\0') {
strcpy(new_info.title, existing->title);
}
printf("Autor atual: %s (pressione Enter para manter)\n", existing->author);
input_string("Novo autor: ", new_info.author, sizeof(new_info.author));
if (new_info.author[0] == '\0') {
strcpy(new_info.author, existing->author);
}
char price_str[32];
printf("Preco atual: %.2f (pressione Enter para manter)\n", existing->price);
input_string("Novo preco: ", price_str, sizeof(price_str));
if (price_str[0] != '\0') {
new_info.price = atof(price_str);
} else {
new_info.price = existing->price;
}
strcpy(new_info.isbn, isbn);
if (library_update(lib, isbn, &new_info)) {
printf("Atualizado com sucesso!\n");
}
}
static void show_menu(void) {
printf("\n===== Sistema de Gerenciamento de Biblioteca =====\n");
printf("1. Adicionar livro\n");
printf("2. Remover livro\n");
printf("3. Buscar livro\n");
printf("4. Atualizar livro\n");
printf("5. Listar todos os livros\n");
printf("6. Salvar dados\n");
printf("0. Sair\n");
printf("==================================================\n");
}
int main(void) {
Library lib;
library_init(&lib);
if (!storage_load(&lib, DATA_FILE)) {
printf("Aviso: Falha ao carregar dados, iniciando com biblioteca vazia\n");
}
char choice[8];
while (1) {
show_menu();
input_string("Escolha uma opcao: ", choice, sizeof(choice));
if (strcmp(choice, "1") == 0) {
cmd_add(&lib);
} else if (strcmp(choice, "2") == 0) {
cmd_remove(&lib);
} else if (strcmp(choice, "3") == 0) {
cmd_find(&lib);
} else if (strcmp(choice, "4") == 0) {
cmd_update(&lib);
} else if (strcmp(choice, "5") == 0) {
library_list(&lib);
} else if (strcmp(choice, "6") == 0) {
if (storage_save(&lib, DATA_FILE)) {
printf("Salvo com sucesso!\n");
} else {
printf("Falha ao salvar!\n");
}
} else if (strcmp(choice, "0") == 0) {
printf("Salvar dados antes de sair? (s/n): ");
char confirm[8];
if (fgets(confirm, sizeof(confirm), stdin) != NULL) {
if (confirm[0] == 's' || confirm[0] == 'S') {
storage_save(&lib, DATA_FILE);
printf("Salvo\n");
}
}
break;
} else {
printf("Opcao invalida\n");
}
}
library_free(&lib);
return 0;
}
Makefile
CC = gcc
CFLAGS = -Wall -Wextra -std=c99 -O2
SRCS = main.c library.c storage.c
OBJS = $(SRCS:.c=.o)
TARGET = bookmanager
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
main.o: main.c library.h storage.h
library.o: library.c library.h
storage.o: storage.c storage.h library.h
clean:
rm -f $(OBJS) $(TARGET)
.PHONY: clean
Compilar e executar:
make
./bookmanager
Executando o Programa
===== Sistema de Gerenciamento de Biblioteca =====
1. Adicionar livro
2. Remover livro
3. Buscar livro
4. Atualizar livro
5. Listar todos os livros
6. Salvar dados
0. Sair
==================================================
Escolha uma opcao: 1
Digite ISBN: 9787115279460
Digite titulo: C Primer Plus
Digite autor: Stephen Prata
Digite preco: 89.00
Adicionado com sucesso!
Escolha uma opcao: 5
ISBN Titulo Autor Preco
--------------------------------------------------------------
9787115279460 C Primer Plus Stephen Prata 89.00
Total: 1 livros
Insights Importantes do Projeto
Expansão do Array Dinâmico
ensure_capacity usa uma estratégia de dobramento. Cada vez que count atinge capacity, a capacidade dobra. Isso significa que n inserções causam O(log n) realocações totais, dando O(1) amortizado por inserção.
Escolha do Formato de Arquivo
O formato de armazenamento usa texto delimitado por | em vez de binário. Benefícios:
- Legível por humanos, fácil de depurar
- Não é afetado por endianness e alinhamento da plataforma
- Pode ser editado diretamente com um editor de texto
Separação de Módulos
library e storage estão desacoplados — a lógica central não depende do método específico de armazenamento. Trocar para um banco de dados depois requer apenas modificar storage.c, sem mudanças no código central.
Segurança de Entrada
input_string usa fgets para entrada, limitando automaticamente o comprimento, depois remove a nova linha com strcspn. Esse é um método de entrada mais seguro que scanf.
❓ Perguntas Frequentes
P: Por que não salvar automaticamente ao sair? R: O usuário pode ter feito alterações acidentais. Perguntar se deseja salvar dá a chance de desfazer. Salvar automaticamente poderia sobrescrever dados anteriores.
P: Por que remover um elemento de um array dinâmico exige deslocamento? R: Porque arrays são armazenados contiguamente na memória, após excluir um elemento do meio, os elementos subsequentes devem deslocar para frente para preencher a lacuna — caso contrário, busca e travessia seriam comprometidas.
P: Quantos livros este projeto pode suportar? R: Teoricamente limitado apenas pela memória. Cada livro tem cerca de 210 bytes, então 1GB de memória poderia armazenar aproximadamente 5 milhões de livros. O gargalo prático é a eficiência de busca — busca linear em O(n) fica lenta com grandes conjuntos de dados; uma tabela hash ou árvore binária de busca seria melhor.
P: O que significa a linha %.o no Makefile? R: É uma regra de padrão significando que todos os arquivos .c são compilados em arquivos .o usando o mesmo comando.
$<é a primeira dependência (arquivo .c),$@é o alvo (arquivo .o).
📖 Resumo
- Análise de requisitos é o ponto de partida — defina funcionalidades antes de escrever código
- Design de estrutura de dados determina a arquitetura do programa; arrays dinâmicos são um contêiner redimensionável comum
- Programação modular: arquivos de cabeçalho declaram interfaces, arquivos fonte implementam detalhes
- Persistência em arquivo garante que os dados sobrevivam após o programa encerrar
- Makefile automatiza builds, evitando entrada manual de comandos longos de compilação
📝 Exercícios
- Adicione uma funcionalidade de "buscar por autor" ao sistema de gerenciamento de biblioteca
- Adicione funcionalidade de ordenação para exibir livros ordenados por título, preço ou ISBN
- Melhore o módulo de armazenamento trocando para formato de arquivo binário e compare os prós e contras versus formato texto



