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:

  1. Adicionar livros (título, autor, ISBN, preço)
  2. Remover livros (por ISBN)
  3. Atualizar informações do livro
  4. Buscar livros (por título ou ISBN)
  5. Listar todos os livros
  6. Salvar dados em arquivo (persistência)
  7. 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:

C
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

C
#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

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);
}
💡 Dica: 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

C
#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

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;
}
⚠️ Atenção: O formato %[^|] 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

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

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:

BASH
make
./bookmanager

Executando o Programa

TEXT
===== 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:

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

📝 Exercícios

  1. Adicione uma funcionalidade de "buscar por autor" ao sistema de gerenciamento de biblioteca
  2. Adicione funcionalidade de ordenação para exibir livros ordenados por título, preço ou ISBN
  3. Melhore o módulo de armazenamento trocando para formato de arquivo binário e compare os prós e contras versus formato texto
100%