Final Project: Library Management System
Building a house isn't just about learning to lay bricks — now it's time to combine all your skills and construct a complete building, from foundation to roof.
Requirements Analysis
We'll build a command-line library management system with the following features:
- Add books (title, author, ISBN, price)
- Remove books (by ISBN)
- Update book information
- Search books (by title or ISBN)
- List all books
- Save data to file (persistence)
- Load data from file
Data Structure Design
Book information is represented by a struct, and all books are managed with a dynamic array:
typedef struct {
char isbn[14];
char title[128];
char author[64];
double price;
} Book;
typedef struct {
Book *books;
int count;
int capacity;
} Library;
Library manages a dynamic array: books is the data pointer, count is the current number of items, and capacity is the allocated capacity. When count reaches capacity, it automatically expands.
Project File Organization
bookmanager/
├── Makefile
├── main.c
├── library.h
├── library.c
├── storage.h
└── storage.c
library.h/library.c: core logic for book management (CRUD)storage.h/storage.c: file I/Omain.c: main program entry point and user interfaceMakefile: build script
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("No books in the library.\n");
return;
}
printf("%-14s %-30s %-20s %-8s\n", "ISBN", "Title", "Author", "Price");
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 books\n", lib->count);
}
ensure_capacity is an internal function, decorated with static to limit its scope to this file. The dynamic array doubling strategy is standard practice, guaranteeing amortized O(1) insertion efficiency.
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;
}
fscanf's %[^|] format reads a string up to the | delimiter. %13[^|] limits the read to at most 13 characters, preventing overflow. This is the safe way to use formatted I/O.
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("Enter ISBN: ", book.isbn, sizeof(book.isbn));
input_string("Enter title: ", book.title, sizeof(book.title));
input_string("Enter author: ", book.author, sizeof(book.author));
char price_str[32];
input_string("Enter price: ", price_str, sizeof(price_str));
book.price = atof(price_str);
if (library_find_by_isbn(lib, book.isbn) != NULL) {
printf("Error: ISBN %s already exists\n", book.isbn);
return;
}
if (library_add(lib, &book)) {
printf("Added successfully!\n");
} else {
printf("Failed to add: out of memory\n");
}
}
static void cmd_remove(Library *lib) {
char isbn[14];
input_string("Enter ISBN to remove: ", isbn, sizeof(isbn));
if (library_remove(lib, isbn)) {
printf("Removed successfully!\n");
} else {
printf("Book with that ISBN not found\n");
}
}
static void cmd_find(Library *lib) {
printf("1. Search by ISBN 2. Search by title\n");
char choice[8];
input_string("Choose: ", choice, sizeof(choice));
if (strcmp(choice, "1") == 0) {
char isbn[14];
input_string("Enter ISBN: ", isbn, sizeof(isbn));
Book *book = library_find_by_isbn(lib, isbn);
if (book != NULL) {
printf("ISBN: %s\n", book->isbn);
printf("Title: %s\n", book->title);
printf("Author: %s\n", book->author);
printf("Price: %.2f\n", book->price);
} else {
printf("Not found\n");
}
} else if (strcmp(choice, "2") == 0) {
char keyword[128];
input_string("Enter keyword: ", keyword, sizeof(keyword));
Book *results[100];
int result_count = 0;
library_find_by_title(lib, keyword, results, &result_count);
if (result_count == 0) {
printf("No books containing \"%s\" found\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("Found %d books\n", result_count);
}
}
}
static void cmd_update(Library *lib) {
char isbn[14];
input_string("Enter ISBN to update: ", isbn, sizeof(isbn));
Book *existing = library_find_by_isbn(lib, isbn);
if (existing == NULL) {
printf("Book with that ISBN not found\n");
return;
}
Book new_info = *existing;
printf("Current title: %s (press Enter to keep)\n", existing->title);
input_string("New title: ", new_info.title, sizeof(new_info.title));
if (new_info.title[0] == '\0') {
strcpy(new_info.title, existing->title);
}
printf("Current author: %s (press Enter to keep)\n", existing->author);
input_string("New author: ", new_info.author, sizeof(new_info.author));
if (new_info.author[0] == '\0') {
strcpy(new_info.author, existing->author);
}
char price_str[32];
printf("Current price: %.2f (press Enter to keep)\n", existing->price);
input_string("New price: ", 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("Updated successfully!\n");
}
}
static void show_menu(void) {
printf("\n===== Library Management System =====\n");
printf("1. Add book\n");
printf("2. Remove book\n");
printf("3. Search book\n");
printf("4. Update book\n");
printf("5. List all books\n");
printf("6. Save data\n");
printf("0. Exit\n");
printf("=====================================\n");
}
int main(void) {
Library lib;
library_init(&lib);
if (!storage_load(&lib, DATA_FILE)) {
printf("Warning: Failed to load data, starting with empty library\n");
}
char choice[8];
while (1) {
show_menu();
input_string("Choose an option: ", 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("Saved successfully!\n");
} else {
printf("Failed to save!\n");
}
} else if (strcmp(choice, "0") == 0) {
printf("Save data before exiting? (y/n): ");
char confirm[8];
if (fgets(confirm, sizeof(confirm), stdin) != NULL) {
if (confirm[0] == 'y' || confirm[0] == 'Y') {
storage_save(&lib, DATA_FILE);
printf("Saved\n");
}
}
break;
} else {
printf("Invalid choice\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
Build and run:
make
./bookmanager
Running the Program
===== Library Management System =====
1. Add book
2. Remove book
3. Search book
4. Update book
5. List all books
6. Save data
0. Exit
=====================================
Choose an option: 1
Enter ISBN: 9787115279460
Enter title: C Primer Plus
Enter author: Stephen Prata
Enter price: 89.00
Added successfully!
Choose an option: 5
ISBN Title Author Price
--------------------------------------------------------------
9787115279460 C Primer Plus Stephen Prata 89.00
Total: 1 books
Key Project Insights
Dynamic Array Expansion
ensure_capacity uses a doubling strategy. Each time count reaches capacity, the capacity doubles. This means n insertions cause O(log n) total reallocations, giving amortized O(1) per insertion.
File Format Choice
The storage format uses |-delimited text rather than binary. Benefits:
- Human-readable, easy to debug
- Not affected by platform endianness and alignment
- Can be directly edited with a text editor
Module Separation
library and storage are decoupled — core logic doesn't depend on the specific storage method. Switching to a database later only requires modifying storage.c, with no changes to the core code.
Input Safety
input_string uses fgets for input, automatically limiting length, then removes the newline with strcspn. This is a safer input method than scanf.
❓ FAQ
$< is the first dependency (.c file), $@ is the target (.o file).📖 Summary
- Requirements analysis is the starting point — define features before writing code
- Data structure design determines program architecture; dynamic arrays are a common resizable container
- Modular programming: header files declare interfaces, source files implement details
- File persistence ensures data survives after the program exits
- Makefile automates builds, avoiding manual entry of long compile commands
📝 Exercises
- Add a "search by author" feature to the library management system
- Add sorting functionality to display books sorted by title, price, or ISBN
- Improve the storage module by switching to binary file format and compare the pros and cons versus text format



