🧱 Section 3 · OOP 🟠 Project MODULE 14

Project — Library Management System

⏱️ 90 min
📖 Hands-on Build
🧩 3 Quiz Questions
🏗️ 1 Extension Task
Your progress in Section 3100%
🎯 The Project: Build a small, genuine Library Management System that combines everything from this section — classes, encapsulation, and clean design. You'll create a Book class and a Library class that can add books, list the catalog, and mark books as borrowed or returned. We'll build it step by step, then see the complete, working program.

Project Brief

Real software is rarely one class in isolation — it's a small group of classes that work together. Here, Library will own and manage a collection of Book objects. This is the same fundamental pattern behind almost every real application: one class represents a single "thing," another class represents a collection of those things plus the operations you can perform on the collection.

📖
Book
Represents a single book: title, author, and whether it's currently borrowed.
🏛️
Library
Owns a collection of Book objects and provides operations: add, list, borrow, return.
📦
A quick preview: std::vector
To hold a growing collection of Book objects, Library will use std::vector<Book> — a resizable array from the C++ Standard Library. You'll learn much more about vector in the very next section, but here's the basic usage you need right now: push_back(item) adds an item to the end, size() returns how many items it holds, and books[i] accesses the item at index i, just like a regular array.

The Build Plan

We'll build this in four steps, each one adding a working piece to the last.

1
Define the Book class
Private data (title, author, borrowed status), a constructor, getters, and simple state-changing methods.
2
Define the Library class
A class that holds a vector<Book> as its private data — the collection Library is responsible for.
3
Add methods
addBook(), listBooks(), borrowBook(), and returnBook() — the operations users of the Library class actually need.
4
Put it together in main()
Create a Library, add some books, and exercise every method to prove the system works end to end.

The Book Class

Each book knows its own title, author, and borrowed status — and controls how that status changes. Nothing outside the class can set borrowed directly; it can only go through borrow() and returnBook().

Step 1 — book.cpp
C++
class Book {
private:
    string title;
    string author;
    bool borrowed;

public:
    Book(string t, string a) {
        title    = t;
        author   = a;
        borrowed = false;   // every new book starts available
    }

    string getTitle()  const { return title; }
    string getAuthor() const { return author; }
    bool   isBorrowed() const { return borrowed; }

    void borrow() { borrowed = true; }
    void returnBook() { borrowed = false; }

    void display() const {
        cout << "\"" << title << "\" by " << author
             << " — " << (borrowed ? "Borrowed" : "Available") << endl;
    }
};

The Library Class Skeleton

Start simple: Library just holds a collection of books. No behaviour yet — that comes next.

Step 2 — library_skeleton.cpp
C++
class Library {
private:
    vector<Book> books;   // the collection this Library owns

public:
    // methods will go here in Step 3
};

Adding the Library's Methods

addBook() constructs a new Book and appends it. listBooks() prints the whole catalog. borrowBook() and returnBook() search for a book by title and change its state — but only if the action makes sense (you can't borrow a book that's already out).

Step 3 — library_methods.cpp
C++
class Library {
private:
    vector<Book> books;

public:
    void addBook(string title, string author) {
        Book newBook(title, author);
        books.push_back(newBook);   // appends to the end of the vector
    }

    void listBooks() const {
        cout << "--- Library Catalog (" << books.size() << " books) ---" << endl;
        for (int i = 0; i < books.size(); i++) {
            cout << i + 1 << ". ";
            books[i].display();
        }
    }

    bool borrowBook(string title) {
        for (int i = 0; i < books.size(); i++) {
            if (books[i].getTitle() == title && !books[i].isBorrowed()) {
                books[i].borrow();
                return true;
            }
        }
        return false;   // not found, or already borrowed
    }

    bool returnBook(string title) {
        for (int i = 0; i < books.size(); i++) {
            if (books[i].getTitle() == title && books[i].isBorrowed()) {
                books[i].returnBook();
                return true;
            }
        }
        return false;
    }
};
Why check conditions before acting
borrowBook() only succeeds if the book exists and isn't already borrowed. This is a small but real design decision: the method enforces the business rule itself, so no calling code needs to remember to check first.

Putting It Together in main()

Here's the complete, correct program — Book, Library, and a main() that exercises every operation.

library_management.cpp — COMPLETE PROGRAM
C++
#include <iostream>
#include <string>
#include <vector>
using namespace std;

class Book {
private:
    string title;
    string author;
    bool borrowed;

public:
    Book(string t, string a) {
        title    = t;
        author   = a;
        borrowed = false;
    }

    string getTitle()  const { return title; }
    string getAuthor() const { return author; }
    bool   isBorrowed() const { return borrowed; }

    void borrow() { borrowed = true; }
    void returnBook() { borrowed = false; }

    void display() const {
        cout << "\"" << title << "\" by " << author
             << " — " << (borrowed ? "Borrowed" : "Available") << endl;
    }
};

class Library {
private:
    vector<Book> books;   // you'll learn more about vector in the next section

public:
    void addBook(string title, string author) {
        Book newBook(title, author);
        books.push_back(newBook);
    }

    void listBooks() const {
        cout << "--- Library Catalog (" << books.size() << " books) ---" << endl;
        for (int i = 0; i < books.size(); i++) {
            cout << i + 1 << ". ";
            books[i].display();
        }
    }

    bool borrowBook(string title) {
        for (int i = 0; i < books.size(); i++) {
            if (books[i].getTitle() == title && !books[i].isBorrowed()) {
                books[i].borrow();
                return true;
            }
        }
        return false;
    }

    bool returnBook(string title) {
        for (int i = 0; i < books.size(); i++) {
            if (books[i].getTitle() == title && books[i].isBorrowed()) {
                books[i].returnBook();
                return true;
            }
        }
        return false;
    }
};

int main() {
    Library library;

    library.addBook("The Hobbit", "J.R.R. Tolkien");
    library.addBook("Dune", "Frank Herbert");
    library.addBook("1984", "George Orwell");

    library.listBooks();

    library.borrowBook("Dune");
    cout << endl << "After borrowing \"Dune\":" << endl;
    library.listBooks();

    library.returnBook("Dune");
    cout << endl << "After returning \"Dune\":" << endl;
    library.listBooks();

    return 0;
}

Output:

Console Output
OUTPUT
--- Library Catalog (3 books) ---
1. "The Hobbit" by J.R.R. Tolkien — Available
2. "Dune" by Frank Herbert — Available
3. "1984" by George Orwell — Available

After borrowing "Dune":
--- Library Catalog (3 books) ---
1. "The Hobbit" by J.R.R. Tolkien — Available
2. "Dune" by Frank Herbert — Borrowed
3. "1984" by George Orwell — Available

After returning "Dune":
--- Library Catalog (3 books) ---
1. "The Hobbit" by J.R.R. Tolkien — Available
2. "Dune" by Frank Herbert — Available
3. "1984" by George Orwell — Available

Notice that main() never touches books directly, and Book's borrowed flag is never set from outside its own methods — every rule about how a book's state can change lives inside the classes themselves. That's the payoff of everything you learned in Section 3.

🧩 Knowledge Check — Lesson 14
A shorter check-in for this project lesson — 3 questions about the design decisions.
1. Why does Library store a vector<Book> instead of a fixed-size array?
2. Why is Book's borrowed flag private, changed only through borrow()/returnBook(), rather than being a public variable?
3. What does library.addBook("Dune", "Frank Herbert") do internally?
💪
Extension Task — Lesson 14
Extend the project · Intermediate Level

The base project works — now make it more useful. Complete this extension in any C++ compiler or online IDE, using the complete program from Section 6 as your starting point.

Extension: Search books by title 🔍

Add a new method to Library:

void searchByTitle(string keyword) const

It should loop through books and print (using display()) every book whose title contains the given keyword as a substring — not just an exact match. For example, searching "the" should match "The Hobbit".

Call it from main() with a few different keywords and confirm it only prints matching books.
💡 Show hints if you're stuck
  • string has a .find(substring) method that returns the position of the first match, or string::npos if there's no match at all
  • Loop condition: if (books[i].getTitle().find(keyword) != string::npos) { books[i].display(); }
  • Print a header first, like cout << "Results for \"" << keyword << "\":" << endl;
  • Bonus: track whether anything matched, and print "No books found." if nothing did
Finished this project?
Mark it complete to track your progress.
🎉

Section 3 Complete!

You've built a real multi-class C++ program using classes, encapsulation, and clean design. Next up: the STL, starting with std::vector — which you've already had a preview of here.

Module 14 of 26 Section 3 — Object-Oriented Programming