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:

  1. Add books (title, author, ISBN, price)
  2. Remove books (by ISBN)
  3. Update book information
  4. Search books (by title or ISBN)
  5. List all books
  6. Save data to file (persistence)
  7. Load data from file

Data Structure Design

Book information is represented by a struct, and all books are managed with a dynamic array:

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

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("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);
}
💡 Tip: 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

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;
}
⚠️ Note: 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

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

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:

BASH
make
./bookmanager

Running the Program

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

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

Q Why not auto-save on exit?
A The user may have made accidental changes. Asking whether to save gives them a chance to undo. Auto-saving could overwrite previous data.
Q Why does removing an element from a dynamic array require shifting?
A Because arrays are stored contiguously in memory, after deleting a middle element, subsequent elements must shift forward to fill the gap — otherwise lookup and traversal would break.
Q How many books can this project support?
A Theoretically limited only by memory. Each book is about 210 bytes, so 1GB of memory could hold roughly 5 million books. The practical bottleneck is search efficiency — linear search at O(n) becomes slow with large datasets; a hash table or binary search tree would be better.
Q What does the %.o line in the Makefile mean?
A It's a pattern rule meaning all .c files are compiled into .o files using the same command. $< is the first dependency (.c file), $@ is the target (.o file).

📖 Summary

📝 Exercises

  1. Add a "search by author" feature to the library management system
  2. Add sorting functionality to display books sorted by title, price, or ISBN
  3. Improve the storage module by switching to binary file format and compare the pros and cons versus text format
100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏