Project — Library Management System
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 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.
vector<Book> as its private data — the collection Library is responsible for.addBook(), listBooks(), borrowBook(), and returnBook() — the operations users of the Library class actually need.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().
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.
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).
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; } };
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.
#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:
--- 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.
Library store a vector<Book> instead of a fixed-size array?Book's borrowed flag private, changed only through borrow()/returnBook(), rather than being a public variable?library.addBook("Dune", "Frank Herbert") do internally?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.
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
stringhas a.find(substring)method that returns the position of the first match, orstring::nposif 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