Module 00 Tutorial
Prerequisites: Review Module 00 Concepts first.
Exercise 00: Megaphone
Section titled “Exercise 00: Megaphone”Subject Analysis
Section titled “Subject Analysis”The Megaphone exercise asks you to create a program that:
- Takes command-line arguments and converts them all to uppercase
- Concatenates all arguments without adding spaces between them
- Prints a default message
* LOUD AND UNBEARABLE FEEDBACK NOISE *when no arguments are given
This is your first C++ program. The goal is to learn basic I/O with std::cout and understand how command-line arguments work in C++.
Approach Strategy
Section titled “Approach Strategy”Before writing any code, think through the problem:
- Understand argc/argv:
argcis the argument count,argvis an array of C-strings.argv[0]is always the program name. - Handle the edge case first: Check if no arguments were given (argc == 1).
- Process each argument: Loop from index 1 to skip the program name.
- Convert character by character: For each argument, loop through each character and convert to uppercase.
- Output at the end: Print a single newline after all characters.
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Basic skeleton:
#include <iostream>
int main(int argc, char **argv){ // TODO: implement return 0;}Stage 2 - Handle no-args case:
#include <iostream>
int main(int argc, char **argv){ if (argc == 1) { std::cout << "* LOUD AND UNBEARABLE FEEDBACK NOISE *" << std::endl; return 0; } // TODO: handle arguments return 0;}Stage 3 - Complete implementation:
#include <iostream>#include <cctype>
int main(int argc, char **argv){ if (argc == 1) { std::cout << "* LOUD AND UNBEARABLE FEEDBACK NOISE *" << std::endl; return 0; }
for (int i = 1; i < argc; i++) { for (int j = 0; argv[i][j]; j++) { std::cout << (char)toupper(argv[i][j]); } } std::cout << std::endl;
return 0;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”| Line | Code | Why |
|---|---|---|
| 1 | #include <iostream> | C++ I/O library. Use this instead of stdio.h. Gives us std::cout. |
| 2 | #include <cctype> | C++ wrapper for character functions. Provides toupper(). |
| 4 | int main(int argc, char **argv) | Standard signature for programs receiving command-line arguments. |
| 6 | if (argc == 1) | Why 1, not 0? Because argv[0] is always the program name, so 1 means “no actual arguments”. |
| 8 | std::cout << ... << std::endl; | Stream the message to stdout. std::endl flushes the buffer and adds newline. |
| 9 | return 0; | Exit early after printing the default message. |
| 12 | for (int i = 1; i < argc; i++) | Start at 1 to skip argv[0] (the program name). |
| 14 | for (int j = 0; argv[i][j]; j++) | Loop until null terminator. argv[i][j] is the j-th character of i-th argument. |
| 16 | (char)toupper(argv[i][j]) | toupper() returns an int, so we cast to char for proper output. |
| 19 | std::cout << std::endl; | Print final newline only after all characters are output. |
Common Pitfalls
Section titled “Common Pitfalls”-
Using printf instead of std::cout
- C habit! In C++ modules, you must use
std::coutfor output. - Wrong:
printf("hello"); - Right:
std::cout << "hello";
- C habit! In C++ modules, you must use
-
Forgetting that toupper() returns int
- Without the cast, you may print ASCII codes instead of characters.
- Wrong:
std::cout << toupper(c); - Right:
std::cout << (char)toupper(c);
-
Forgetting the no-argument case
- The subject explicitly requires printing
* LOUD AND UNBEARABLE FEEDBACK NOISE *when there are no arguments. - Always test with
./megaphone(no args).
- The subject explicitly requires printing
-
Adding spaces between arguments
- The subject wants arguments concatenated directly.
./megaphone "Hello" "World"should outputHELLOWORLD, notHELLO WORLD.
Testing Tips
Section titled “Testing Tips”Test your program with the exact examples from the subject:
./megaphone "shhhhh... I think the students are asleep..."# Expected: SHHHHH... I THINK THE STUDENTS ARE ASLEEP...
./megaphone Damnit " ! " "Sorry students, I thought this thing was off."# Expected: DAMNIT ! SORRY STUDENTS, I THOUGHT THIS THING WAS OFF.
./megaphone# Expected: * LOUD AND UNBEARABLE FEEDBACK NOISE *Additional edge cases to verify:
./megaphone ""# Expected: (empty line - just a newline)
./megaphone "123" "!@#"# Expected: 123!@# (non-letters pass through unchanged)Final Code
Section titled “Final Code”#include <iostream>#include <cctype>
int main(int argc, char **argv){ if (argc == 1) { std::cout << "* LOUD AND UNBEARABLE FEEDBACK NOISE *" << std::endl; return 0; }
for (int i = 1; i < argc; i++) { for (int j = 0; argv[i][j]; j++) { std::cout << (char)toupper(argv[i][j]); } } std::cout << std::endl;
return 0;}Exercise 01: PhoneBook
Section titled “Exercise 01: PhoneBook”Your first real C++ program with classes. This exercise teaches you object-oriented fundamentals: encapsulation, class design, and how objects interact.
Subject Analysis
Section titled “Subject Analysis”The PhoneBook exercise requires you to build a contact management system with these constraints:
Two classes needed:
- Contact: Stores data for a single person (first name, last name, nickname, phone number, darkest secret)
- PhoneBook: Manages a collection of up to 8 contacts
Core requirements:
- Fixed array of 8 contacts - NO vectors, NO dynamic allocation (
new/delete) - Three commands:
ADD,SEARCH,EXIT - Circular buffer: When full, oldest contact gets replaced
- No empty fields: Every field must have content
- SEARCH display: 10-character columns, right-aligned, truncate with ”.” if too long
- EOF handling: Program must exit gracefully on Ctrl+D
The SEARCH command has two phases:
- Display a formatted table showing all contacts
- Prompt for an index, then display that contact’s full details
Approach Strategy
Section titled “Approach Strategy”Think through the design before coding:
Step 1 - Design the Contact class first
Ask yourself: What data does a contact hold? What methods does it need?
- 5 private string attributes (the fields)
- A flag to know if the slot is used (
_isEmpty) - Setters to store data, getters to retrieve it
- Everything private except the interface methods
Step 2 - Design the PhoneBook class
Ask yourself: How do I manage 8 contacts? How do I know where to add next?
- Fixed array:
Contact _contacts[8] - Track where to add next:
_currentIndex(cycles 0→7→0) - Track how many exist:
_totalContacts(caps at 8)
Step 3 - Plan the main loop
while (true) { read command if ADD → call phonebook.addContact() if SEARCH → call phonebook.searchContacts() if EXIT → break}Step 4 - Handle edge cases
- Empty input rejection (loop until non-empty)
- EOF handling (
std::getlinereturns false) - Invalid index (check range and format)
Key insight - The circular buffer:
You need two trackers:
_currentIndex: Where to add the NEXT contact (wraps with modulo 8)_totalContacts: How many contacts exist (maximum 8)
When adding contact #9, _currentIndex will be 0, replacing the oldest contact.
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Contact Class (the data container):
#ifndef CONTACT_HPP#define CONTACT_HPP
#include <string>
class Contact {private: std::string _firstName; std::string _lastName; std::string _nickname; std::string _phoneNumber; std::string _darkestSecret; bool _isEmpty;
public: Contact(); ~Contact();
// Setters - take const reference for efficiency void setFirstName(const std::string& firstName); void setLastName(const std::string& lastName); void setNickname(const std::string& nickname); void setPhoneNumber(const std::string& phoneNumber); void setDarkestSecret(const std::string& secret); void setIsEmpty(bool isEmpty);
// Getters - marked const (promise not to modify object) std::string getFirstName() const; std::string getLastName() const; std::string getNickname() const; std::string getPhoneNumber() const; std::string getDarkestSecret() const; bool isEmpty() const;};
#endifWhy these design decisions?
| Decision | Reason |
|---|---|
| Private attributes | Encapsulation - external code can’t corrupt data |
const std::string& params | Avoids copying the string, more efficient |
const on getters | Promises these methods won’t modify the object |
_isEmpty flag | Lets us check if a contact slot is actually used |
| Underscore prefix | Common convention to identify private members |
Stage 2 - PhoneBook Class (the manager):
#ifndef PHONEBOOK_HPP#define PHONEBOOK_HPP
#include "Contact.hpp"#include <string>
class PhoneBook {private: Contact _contacts[8]; // Fixed array - NO vectors! int _currentIndex; // Where to add next (0-7, wraps) int _totalContacts; // How many stored (max 8)
// Private helper methods - internal implementation details std::string _truncate(const std::string& str) const; void _displayContactRow(int index) const; std::string _getInput(const std::string& prompt) const;
public: PhoneBook(); ~PhoneBook();
void addContact(); void searchContacts() const;};
#endifWhy Contact _contacts[8] and not a vector?
The subject explicitly forbids containers from the STL. This forces you to understand how fixed-size arrays work in C++. The array is allocated on the stack as part of the PhoneBook object itself.
Stage 3 - The Circular Buffer Logic:
void PhoneBook::addContact() { Contact newContact;
// Collect all fields (helper rejects empty input) newContact.setFirstName(_getInput("Enter first name: ")); newContact.setLastName(_getInput("Enter last name: ")); newContact.setNickname(_getInput("Enter nickname: ")); newContact.setPhoneNumber(_getInput("Enter phone number: ")); newContact.setDarkestSecret(_getInput("Enter darkest secret: ")); newContact.setIsEmpty(false);
// Store at current index (overwrites oldest if full) _contacts[_currentIndex] = newContact;
// Circular buffer: wrap around using modulo _currentIndex = (_currentIndex + 1) % 8;
// Track count (cap at 8) if (_totalContacts < 8) _totalContacts++;
std::cout << "Contact added successfully!" << std::endl;}The modulo trick explained:
(_currentIndex + 1) % 8 makes the index cycle: 0→1→2→3→4→5→6→7→0→1→2…
_currentIndex | + 1 | % 8 | Result |
|---|---|---|---|
| 0 | 1 | 1 % 8 | 1 |
| 6 | 7 | 7 % 8 | 7 |
| 7 | 8 | 8 % 8 | 0 ← wraps! |
Stage 4 - SEARCH Display Formatting:
std::string PhoneBook::_truncate(const std::string& str) const { if (str.length() > 10) return str.substr(0, 9) + "."; // 9 chars + dot = 10 return str;}
void PhoneBook::_displayContactRow(int index) const { std::cout << std::setw(10) << std::right << index << "|"; std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getFirstName()) << "|"; std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getLastName()) << "|"; std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getNickname()) << std::endl;}Understanding std::setw and std::right:
std::setw(10)- Set width to 10 characters for the next outputstd::right- Align content to the right within that width
Example: "John" becomes " John" (6 spaces + 4 chars = 10)
Line-by-Line Explanation
Section titled “Line-by-Line Explanation”Input validation with EOF handling:
std::string PhoneBook::_getInput(const std::string& prompt) const { std::string input;
while (true) { std::cout << prompt; if (!std::getline(std::cin, input)) { // Returns false on EOF std::cout << std::endl; std::exit(0); // Clean exit on Ctrl+D } if (!input.empty()) break; std::cout << "Field cannot be empty. Please try again." << std::endl; } return input;}| Line | Why |
|---|---|
std::getline(std::cin, input) | Reads entire line including spaces (unlike std::cin >>) |
if (!std::getline(...)) | getline returns false when EOF (Ctrl+D) is encountered |
std::exit(0) | Terminates program cleanly - required for EOF handling |
if (!input.empty()) | Rejects empty strings, keeps prompting |
Index validation for SEARCH:
// Get and validate indexstd::string indexStr;std::cout << "Enter index to view details: ";if (!std::getline(std::cin, indexStr)) { std::cout << std::endl; return;}
// Strict validation: must be single digit 0-7if (indexStr.length() != 1 || indexStr[0] < '0' || indexStr[0] > '7') { std::cout << "Invalid index." << std::endl; return;}
int index = indexStr[0] - '0'; // Convert char digit to intif (index >= _totalContacts) { std::cout << "Invalid index." << std::endl; return;}| Line | Why |
|---|---|
indexStr.length() != 1 | Only accept single character input |
indexStr[0] < '0' || indexStr[0] > '7' | Must be digit 0-7 |
indexStr[0] - '0' | Character to int conversion: '5' - '0' = 5 |
index >= _totalContacts | Can’t access contact that doesn’t exist |
Common Pitfalls
Section titled “Common Pitfalls”1. Using vectors or dynamic allocation
The subject EXPLICITLY forbids this. You will fail the evaluation.
// WRONG - forbiddenstd::vector<Contact> _contacts;Contact* _contacts = new Contact[8];
// RIGHT - fixed arrayContact _contacts[8];2. Wrong display formatting
// WRONG - no formattingstd::cout << firstName << "|" << lastName << std::endl;
// RIGHT - 10 chars, right-aligned, truncatedstd::cout << std::setw(10) << std::right;if (firstName.length() > 10) std::cout << firstName.substr(0, 9) + ".";else std::cout << firstName;3. Not handling the circular buffer correctly
// WRONG - just keeps incrementing_currentIndex++;
// RIGHT - wraps around_currentIndex = (_currentIndex + 1) % 8;4. Accepting empty fields
// WRONG - accepts anythingstd::getline(std::cin, input);contact.setFirstName(input);
// RIGHT - loop until non-emptywhile (input.empty()) { std::cout << "Field cannot be empty: "; std::getline(std::cin, input);}5. Not handling EOF (Ctrl+D)
// WRONG - ignores EOF, program hangsstd::getline(std::cin, input);
// RIGHT - check return value and exitif (!std::getline(std::cin, input)) { std::cout << std::endl; std::exit(0);}6. Making attributes public
// WRONG - breaks encapsulationclass Contact {public: std::string firstName; // Anyone can modify directly!};
// RIGHT - private with accessorsclass Contact {private: std::string _firstName;public: void setFirstName(const std::string& name); std::string getFirstName() const;};Testing Tips
Section titled “Testing Tips”# Test ADD functionality> ADDEnter first name: JohnEnter last name: DoeEnter nickname: JDEnter phone number: 555-1234Enter darkest secret: Likes pineapple pizzaContact added successfully!
# Test empty field rejection> ADDEnter first name:Field cannot be empty. Please try again.Enter first name: Jane
# Test SEARCH with no contacts> SEARCHPhoneBook is empty.
# Test SEARCH display formatting> SEARCH Index|First Name| Last Name| Nickname-------------------------------------------- 0| John| Doe| JDEnter index to view details: 0First Name: JohnLast Name: DoeNickname: JDPhone Number: 555-1234Darkest Secret: Likes pineapple pizza
# Test truncation (name > 10 chars)# Add contact with first name "Christopher"> SEARCH# Should display "Christoph." (9 chars + dot)
# Test circular buffer (add 9 contacts)# Contact #9 should replace contact #0
# Test invalid index> SEARCHEnter index to view details: 9Invalid index.
> SEARCHEnter index to view details: abcInvalid index.
# Test EOF handling> (Press Ctrl+D)# Program should exit cleanly with newlineFinal Code
Section titled “Final Code”Contact.hpp
#ifndef CONTACT_HPP#define CONTACT_HPP
#include <string>
class Contact {private: std::string _firstName; std::string _lastName; std::string _nickname; std::string _phoneNumber; std::string _darkestSecret; bool _isEmpty;
public: Contact(); ~Contact();
void setFirstName(const std::string& firstName); void setLastName(const std::string& lastName); void setNickname(const std::string& nickname); void setPhoneNumber(const std::string& phoneNumber); void setDarkestSecret(const std::string& secret); void setIsEmpty(bool isEmpty);
std::string getFirstName() const; std::string getLastName() const; std::string getNickname() const; std::string getPhoneNumber() const; std::string getDarkestSecret() const; bool isEmpty() const;};
#endifContact.cpp
#include "Contact.hpp"
Contact::Contact() : _isEmpty(true) {}
Contact::~Contact() {}
void Contact::setFirstName(const std::string& firstName) { _firstName = firstName; }void Contact::setLastName(const std::string& lastName) { _lastName = lastName; }void Contact::setNickname(const std::string& nickname) { _nickname = nickname; }void Contact::setPhoneNumber(const std::string& phoneNumber) { _phoneNumber = phoneNumber; }void Contact::setDarkestSecret(const std::string& secret) { _darkestSecret = secret; }void Contact::setIsEmpty(bool isEmpty) { _isEmpty = isEmpty; }
std::string Contact::getFirstName() const { return _firstName; }std::string Contact::getLastName() const { return _lastName; }std::string Contact::getNickname() const { return _nickname; }std::string Contact::getPhoneNumber() const { return _phoneNumber; }std::string Contact::getDarkestSecret() const { return _darkestSecret; }bool Contact::isEmpty() const { return _isEmpty; }PhoneBook.hpp
#ifndef PHONEBOOK_HPP#define PHONEBOOK_HPP
#include "Contact.hpp"#include <string>
class PhoneBook {private: Contact _contacts[8]; int _currentIndex; int _totalContacts;
std::string _truncate(const std::string& str) const; void _displayContactRow(int index) const; std::string _getInput(const std::string& prompt) const;
public: PhoneBook(); ~PhoneBook();
void addContact(); void searchContacts() const;};
#endifPhoneBook.cpp
#include "PhoneBook.hpp"#include <iostream>#include <iomanip>#include <cstdlib>
PhoneBook::PhoneBook() : _currentIndex(0), _totalContacts(0) {}
PhoneBook::~PhoneBook() {}
std::string PhoneBook::_truncate(const std::string& str) const { if (str.length() > 10) return str.substr(0, 9) + "."; return str;}
void PhoneBook::_displayContactRow(int index) const { std::cout << std::setw(10) << std::right << index << "|"; std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getFirstName()) << "|"; std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getLastName()) << "|"; std::cout << std::setw(10) << std::right << _truncate(_contacts[index].getNickname()) << std::endl;}
std::string PhoneBook::_getInput(const std::string& prompt) const { std::string input;
while (true) { std::cout << prompt; if (!std::getline(std::cin, input)) { std::cout << std::endl; std::exit(0); } if (!input.empty()) break; std::cout << "Field cannot be empty. Please try again." << std::endl; } return input;}
void PhoneBook::addContact() { Contact newContact;
newContact.setFirstName(_getInput("Enter first name: ")); newContact.setLastName(_getInput("Enter last name: ")); newContact.setNickname(_getInput("Enter nickname: ")); newContact.setPhoneNumber(_getInput("Enter phone number: ")); newContact.setDarkestSecret(_getInput("Enter darkest secret: ")); newContact.setIsEmpty(false);
_contacts[_currentIndex] = newContact; _currentIndex = (_currentIndex + 1) % 8; if (_totalContacts < 8) _totalContacts++;
std::cout << "Contact added successfully!" << std::endl;}
void PhoneBook::searchContacts() const { if (_totalContacts == 0) { std::cout << "PhoneBook is empty." << std::endl; return; }
// Display header std::cout << std::setw(10) << "Index" << "|"; std::cout << std::setw(10) << "First Name" << "|"; std::cout << std::setw(10) << "Last Name" << "|"; std::cout << std::setw(10) << "Nickname" << std::endl; std::cout << std::string(44, '-') << std::endl;
// Display all contacts for (int i = 0; i < _totalContacts; i++) _displayContactRow(i);
// Get index from user std::cout << "Enter index to view details: "; std::string indexStr; if (!std::getline(std::cin, indexStr)) { std::cout << std::endl; return; }
// Validate index if (indexStr.length() != 1 || indexStr[0] < '0' || indexStr[0] > '7') { std::cout << "Invalid index." << std::endl; return; }
int index = indexStr[0] - '0'; if (index >= _totalContacts) { std::cout << "Invalid index." << std::endl; return; }
// Display full contact details std::cout << "First Name: " << _contacts[index].getFirstName() << std::endl; std::cout << "Last Name: " << _contacts[index].getLastName() << std::endl; std::cout << "Nickname: " << _contacts[index].getNickname() << std::endl; std::cout << "Phone Number: " << _contacts[index].getPhoneNumber() << std::endl; std::cout << "Darkest Secret: " << _contacts[index].getDarkestSecret() << std::endl;}main.cpp
#include "PhoneBook.hpp"#include <iostream>#include <cstdlib>
int main() { PhoneBook phonebook; std::string command;
while (true) { std::cout << "Enter command (ADD, SEARCH, EXIT): "; if (!std::getline(std::cin, command)) { std::cout << std::endl; break; }
if (command == "ADD") phonebook.addContact(); else if (command == "SEARCH") phonebook.searchContacts(); else if (command == "EXIT") break; }
return 0;}Exercise 02: The Job Of Your Dreams (Bonus)
Section titled “Exercise 02: The Job Of Your Dreams (Bonus)”Note: This is a BONUS exercise. Complete it after ex00 and ex01 if you want extra practice with static members and reverse-engineering.
This exercise is different. Instead of building from scratch, you’re given pieces and must deduce the missing parts. You receive:
Account.hpp- The complete class declarationtests.cpp- Test code that uses the class- A log file - The expected output showing what should happen
Your job: Write Account.cpp to make the tests produce output matching the log (except timestamps).
Subject Analysis
Section titled “Subject Analysis”What you’re given:
The header file Account.hpp declares:
- Static members tracking global state (
_nbAccounts,_totalAmount, etc.) - Instance members for each account (
_accountIndex,_amount, etc.) - A private
_displayTimestamp()method - Constructor, destructor, and various methods
What you must figure out:
- How to initialize static members (they MUST be defined somewhere)
- How to format the timestamp from the log:
[YYYYMMDD_HHMMSS] - The exact output format (semicolon-separated
key:valuepairs) - What each method should print and when
Approach Strategy
Section titled “Approach Strategy”Step 1 - Analyze the header file
Read every line of Account.hpp. List what you need to implement:
- 4 static members need initialization
- 4 static getter methods
- 1 static display method (
displayAccountsInfos) - Constructor and destructor
makeDeposit,makeWithdrawal,checkAmount,displayStatus- Private
_displayTimestamp
Step 2 - Study the log file
The log shows EXACTLY what output to produce. Key observations:
- Every line starts with a timestamp:
[19920104_091532] - Format is semicolon-separated:
index:0;amount:42;created - Destructor calls happen in REVERSE order of construction
Step 3 - Map log lines to methods
| Log Pattern | Method |
|---|---|
accounts:N;total:N;deposits:N;withdrawals:N | displayAccountsInfos() |
index:N;amount:N;created | Constructor |
index:N;amount:N;closed | Destructor |
index:N;p_amount:N;deposit:N;amount:N;nb_deposits:N | makeDeposit() |
index:N;p_amount:N;withdrawal:refused | makeWithdrawal() (fail) |
index:N;p_amount:N;withdrawal:N;amount:N;nb_withdrawals:N | makeWithdrawal() (success) |
Progressive Code Building
Section titled “Progressive Code Building”Stage 1 - Static member initialization:
#include "Account.hpp"
// CRITICAL: Static members MUST be initialized outside the class// This allocates actual storage for themint Account::_nbAccounts = 0;int Account::_totalAmount = 0;int Account::_totalNbDeposits = 0;int Account::_totalNbWithdrawals = 0;Why outside the class? Static members are shared by ALL instances. The header only declares them; you must define (allocate storage for) them exactly once in a .cpp file.
Stage 2 - Timestamp formatting:
#include <ctime>#include <iomanip>
void Account::_displayTimestamp() { std::time_t now = std::time(NULL); std::tm* local = std::localtime(&now);
std::cout << "[" << (local->tm_year + 1900) // Year since 1900 << std::setfill('0') << std::setw(2) << (local->tm_mon + 1) // Month 0-11 << std::setfill('0') << std::setw(2) << local->tm_mday << "_" << std::setfill('0') << std::setw(2) << local->tm_hour << std::setfill('0') << std::setw(2) << local->tm_min << std::setfill('0') << std::setw(2) << local->tm_sec << "] ";}Understanding <ctime>:
| Function | Purpose |
|---|---|
std::time(NULL) | Get current time as seconds since epoch |
std::localtime(&now) | Convert to local time struct (tm) |
tm->tm_year | Years since 1900 (add 1900 for actual year) |
tm->tm_mon | Month 0-11 (add 1 for human-readable) |
Stage 3 - Constructor and destructor:
Account::Account(int initial_deposit) : _accountIndex(_nbAccounts), // Capture current count BEFORE increment _amount(initial_deposit), _nbDeposits(0), _nbWithdrawals(0) { _nbAccounts++; // NOW increment for next account _totalAmount += initial_deposit;
_displayTimestamp(); std::cout << "index:" << _accountIndex << ";amount:" << _amount << ";created" << std::endl;}
Account::~Account() { _displayTimestamp(); std::cout << "index:" << _accountIndex << ";amount:" << _amount << ";closed" << std::endl;}Why does destructor order matter?
C++ destroys objects in REVERSE order of construction. For arrays:
- First created (index 0) → last destroyed
- Last created (index 7) → first destroyed
The log file shows this clearly - check that your destructor output matches.
Line-by-Line Explanation
Section titled “Line-by-Line Explanation”The withdrawal logic:
bool Account::makeWithdrawal(int withdrawal) { _displayTimestamp(); std::cout << "index:" << _accountIndex << ";p_amount:" << _amount // Previous amount (before) << ";withdrawal:";
if (withdrawal > _amount) { std::cout << "refused" << std::endl; // Can't withdraw more than balance return false; }
_amount -= withdrawal; _nbWithdrawals++; _totalAmount -= withdrawal; // Update global tracker _totalNbWithdrawals++;
std::cout << withdrawal << ";amount:" << _amount // New amount (after) << ";nb_withdrawals:" << _nbWithdrawals << std::endl; return true;}| Line | Why |
|---|---|
p_amount output first | Log shows “previous amount” before withdrawal attempt |
Check withdrawal > _amount | Can’t go negative - refuse if insufficient funds |
| Update both instance AND static | _amount is this account, _totalAmount is all accounts |
Return bool | Tests may check success/failure |
Common Pitfalls
Section titled “Common Pitfalls”1. Initializing static members in the header
// WRONG - in Account.hppclass Account {private: static int _nbAccounts = 0; // ERROR: can't initialize here (pre-C++17)};
// RIGHT - in Account.cppint Account::_nbAccounts = 0; // Definition with initialization2. Forgetting to add 1900/1 to time values
// WRONGstd::cout << local->tm_year; // Prints "125" for year 2025!std::cout << local->tm_mon; // Prints "0" for January!
// RIGHTstd::cout << (local->tm_year + 1900); // Prints "2025"std::cout << (local->tm_mon + 1); // Prints "1"3. Wrong output format
// WRONG - spaces and different separatorsstd::cout << "index: " << _accountIndex << ", amount: " << _amount;
// RIGHT - match the log exactlystd::cout << "index:" << _accountIndex << ";amount:" << _amount;4. Not understanding destructor order
When you create accounts 0, 1, 2… they’re destroyed 2, 1, 0. The log file confirms this - verify your output matches.
Testing Tips
Section titled “Testing Tips”Since you have a reference log file, testing is straightforward:
# Compile your implementationc++ -Wall -Wextra -Werror Account.cpp tests.cpp -o account_test
# Run and capture output./account_test > my_output.txt
# Compare with reference (ignoring timestamps)# The timestamps will differ, but everything else should match
# Strip timestamps for comparisonsed 's/\[[0-9_]*\]/[TIMESTAMP]/g' my_output.txt > my_clean.txtsed 's/\[[0-9_]*\]/[TIMESTAMP]/g' reference.log > ref_clean.txtdiff my_clean.txt ref_clean.txtWhat to verify:
- Account creation messages appear in order (0, 1, 2…)
- Account destruction messages appear in REVERSE order
- Withdrawal refusals say exactly “refused”
- All semicolons and colons are in the right places
Final Code
Section titled “Final Code”#include "Account.hpp"#include <iostream>#include <iomanip>#include <ctime>
// Initialize static membersint Account::_nbAccounts = 0;int Account::_totalAmount = 0;int Account::_totalNbDeposits = 0;int Account::_totalNbWithdrawals = 0;
// Static gettersint Account::getNbAccounts() { return _nbAccounts; }int Account::getTotalAmount() { return _totalAmount; }int Account::getNbDeposits() { return _totalNbDeposits; }int Account::getNbWithdrawals() { return _totalNbWithdrawals; }
// Display timestamp: [YYYYMMDD_HHMMSS]void Account::_displayTimestamp() { std::time_t now = std::time(NULL); std::tm* local = std::localtime(&now);
std::cout << "[" << (local->tm_year + 1900) << std::setfill('0') << std::setw(2) << (local->tm_mon + 1) << std::setfill('0') << std::setw(2) << local->tm_mday << "_" << std::setfill('0') << std::setw(2) << local->tm_hour << std::setfill('0') << std::setw(2) << local->tm_min << std::setfill('0') << std::setw(2) << local->tm_sec << "] ";}
// Display all accounts infovoid Account::displayAccountsInfos() { _displayTimestamp(); std::cout << "accounts:" << _nbAccounts << ";total:" << _totalAmount << ";deposits:" << _totalNbDeposits << ";withdrawals:" << _totalNbWithdrawals << std::endl;}
// ConstructorAccount::Account(int initial_deposit) : _accountIndex(_nbAccounts), _amount(initial_deposit), _nbDeposits(0), _nbWithdrawals(0) { _nbAccounts++; _totalAmount += initial_deposit;
_displayTimestamp(); std::cout << "index:" << _accountIndex << ";amount:" << _amount << ";created" << std::endl;}
// DestructorAccount::~Account() { _displayTimestamp(); std::cout << "index:" << _accountIndex << ";amount:" << _amount << ";closed" << std::endl;}
// Make a depositvoid Account::makeDeposit(int deposit) { _displayTimestamp(); std::cout << "index:" << _accountIndex << ";p_amount:" << _amount << ";deposit:" << deposit;
_amount += deposit; _nbDeposits++; _totalAmount += deposit; _totalNbDeposits++;
std::cout << ";amount:" << _amount << ";nb_deposits:" << _nbDeposits << std::endl;}
// Make a withdrawalbool Account::makeWithdrawal(int withdrawal) { _displayTimestamp(); std::cout << "index:" << _accountIndex << ";p_amount:" << _amount << ";withdrawal:";
if (withdrawal > _amount) { std::cout << "refused" << std::endl; return false; }
_amount -= withdrawal; _nbWithdrawals++; _totalAmount -= withdrawal; _totalNbWithdrawals++;
std::cout << withdrawal << ";amount:" << _amount << ";nb_withdrawals:" << _nbWithdrawals << std::endl; return true;}
// Check amountint Account::checkAmount() const { return _amount;}
// Display statusvoid Account::displayStatus() const { _displayTimestamp(); std::cout << "index:" << _accountIndex << ";amount:" << _amount << ";deposits:" << _nbDeposits << ";withdrawals:" << _nbWithdrawals << std::endl;}