Skip to content

Module 01 Solutions

Download Module 01 Solutions

Stack vs heap allocation - newZombie() returns heap, randomChump() uses stack.

Zombie.hpp
#ifndef ZOMBIE_HPP
#define ZOMBIE_HPP
#include <string>
class Zombie {
private:
std::string _name;
public:
Zombie(std::string name);
~Zombie();
void announce() const;
};
Zombie* newZombie(std::string name); // Heap - caller must delete
void randomChump(std::string name); // Stack - auto cleanup
#endif

Allocate N zombies in a single allocation using new Zombie[N].

Zombie.hpp
#ifndef ZOMBIE_HPP
#define ZOMBIE_HPP
#include <string>
class Zombie {
private:
std::string _name;
public:
Zombie(); // Default constructor needed for array allocation
~Zombie();
void setName(std::string name);
void announce() const;
};
Zombie* zombieHorde(int N, std::string name);
#endif
Zombie.cpp
#include "Zombie.hpp"
#include <iostream>
Zombie::Zombie() {}
Zombie::~Zombie() {
std::cout << _name << " is dead." << std::endl;
}
void Zombie::setName(std::string name) {
_name = name;
}
void Zombie::announce() const {
std::cout << _name << ": BraiiiiiiinnnzzzZ..." << std::endl;
}
zombieHorde.cpp
#include "Zombie.hpp"
Zombie* zombieHorde(int N, std::string name) {
if (N <= 0)
return NULL;
// Single allocation for N zombies
Zombie* horde = new Zombie[N];
// Initialize each zombie with the name
for (int i = 0; i < N; i++)
horde[i].setName(name);
return horde;
}
main.cpp
#include "Zombie.hpp"
int main() {
int N = 5;
Zombie* horde = zombieHorde(N, "Walker");
for (int i = 0; i < N; i++)
horde[i].announce();
delete[] horde; // MUST use delete[] for arrays
return 0;
}

Key Points:

  • Default constructor required for new Zombie[N] (array allocation)
  • Use setName() to initialize after allocation
  • Return pointer to first zombie (array decays to pointer)
  • Caller must use delete[] (not delete) to free array

Demonstrates references as aliases.

main.cpp
#include <iostream>
#include <string>
int main() {
std::string str = "HI THIS IS BRAIN";
std::string* stringPTR = &str;
std::string& stringREF = str;
// All print the same address
std::cout << &str << std::endl;
std::cout << stringPTR << std::endl;
std::cout << &stringREF << std::endl;
// All print the same value
std::cout << str << std::endl;
std::cout << *stringPTR << std::endl;
std::cout << stringREF << std::endl;
return 0;
}

When to use reference (HumanA) vs pointer (HumanB).

Weapon.hpp
#ifndef WEAPON_HPP
#define WEAPON_HPP
#include <string>
class Weapon {
private:
std::string _type;
public:
Weapon(std::string type);
~Weapon();
const std::string& getType() const; // Returns const reference
void setType(std::string type);
};
#endif
Weapon.cpp
#include "Weapon.hpp"
Weapon::Weapon(std::string type) : _type(type) {}
Weapon::~Weapon() {}
const std::string& Weapon::getType() const {
return _type;
}
void Weapon::setType(std::string type) {
_type = type;
}
HumanA.hpp
#ifndef HUMANA_HPP
#define HUMANA_HPP
#include <string>
#include "Weapon.hpp"
class HumanA {
private:
std::string _name;
Weapon& _weapon; // Reference: MUST have weapon, initialized in constructor
public:
HumanA(std::string name, Weapon& weapon);
~HumanA();
void attack() const;
};
#endif
HumanA.cpp
#include "HumanA.hpp"
#include <iostream>
// Reference must be initialized in initializer list
HumanA::HumanA(std::string name, Weapon& weapon)
: _name(name), _weapon(weapon) {}
HumanA::~HumanA() {}
void HumanA::attack() const {
std::cout << _name << " attacks with their " << _weapon.getType() << std::endl;
}
HumanB.hpp
#ifndef HUMANB_HPP
#define HUMANB_HPP
#include <string>
#include "Weapon.hpp"
class HumanB {
private:
std::string _name;
Weapon* _weapon; // Pointer: might not have weapon (can be NULL)
public:
HumanB(std::string name);
~HumanB();
void setWeapon(Weapon& weapon);
void attack() const;
};
#endif
HumanB.cpp
#include "HumanB.hpp"
#include <iostream>
HumanB::HumanB(std::string name) : _name(name), _weapon(NULL) {}
HumanB::~HumanB() {}
void HumanB::setWeapon(Weapon& weapon) {
_weapon = &weapon; // Store address of the weapon
}
void HumanB::attack() const {
if (_weapon)
std::cout << _name << " attacks with their " << _weapon->getType() << std::endl;
else
std::cout << _name << " has no weapon to attack with!" << std::endl;
}

Key Points:

  • HumanA uses reference (Weapon&): Must always have a weapon, initialized at construction
  • HumanB uses pointer (Weapon*): Weapon is optional, can be set later or never
  • Reference must be initialized in constructor’s initializer list (cannot be NULL)
  • Pointer can be NULL and reassigned
  • getType() returns const std::string& to avoid copying and prevent modification
  • When club.setType() is called, both humans see the change (same weapon object)

Replace all occurrences of s1 with s2 without using std::string::replace.

main.cpp
#include <iostream>
#include <fstream>
#include <string>
std::string replaceAll(const std::string& content,
const std::string& s1,
const std::string& s2) {
if (s1.empty())
return content;
std::string result;
std::size_t pos = 0;
std::size_t found;
while ((found = content.find(s1, pos)) != std::string::npos) {
// Append everything before the match
result.append(content, pos, found - pos);
// Append the replacement
result.append(s2);
// Move past the match
pos = found + s1.length();
}
// Append the remainder
result.append(content, pos, std::string::npos);
return result;
}
int main(int argc, char** argv) {
if (argc != 4) {
std::cerr << "Usage: " << argv[0] << " <filename> <s1> <s2>" << std::endl;
return 1;
}
std::string filename = argv[1];
std::string s1 = argv[2];
std::string s2 = argv[3];
// Open input file
std::ifstream inFile(filename.c_str());
if (!inFile.is_open()) {
std::cerr << "Error: Cannot open file '" << filename << "'" << std::endl;
return 1;
}
// Read entire file content
std::string content;
std::string line;
bool first = true;
while (std::getline(inFile, line)) {
if (!first)
content += '\n';
content += line;
first = false;
}
inFile.close();
// Replace all occurrences
std::string result = replaceAll(content, s1, s2);
// Write to output file
std::ofstream outFile((filename + ".replace").c_str());
if (!outFile.is_open()) {
std::cerr << "Error: Cannot create output file" << std::endl;
return 1;
}
outFile << result;
outFile.close();
return 0;
}

Key Points:

  • FORBIDDEN: std::string::replace() - use find() + substr()/append() instead
  • Use std::ifstream for reading, std::ofstream for writing
  • In C++98, use .c_str() to convert std::string to const char* for file streams
  • Output file name is <filename>.replace
  • Handle edge cases: empty s1 (would cause infinite loop), file not found
  • The algorithm: find → append before match → append replacement → repeat

Pointers to member functions - avoid if/else chains.

Harl.cpp
void Harl::complain(std::string level) {
void (Harl::*funcs[4])() = {
&Harl::debug,
&Harl::info,
&Harl::warning,
&Harl::error
};
std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"};
for (int i = 0; i < 4; i++) {
if (level == levels[i]) {
(this->*funcs[i])();
return;
}
}
}

Switch with fall-through for filtering.

main.cpp
int getLevel(const std::string& level) {
std::string levels[4] = {"DEBUG", "INFO", "WARNING", "ERROR"};
for (int i = 0; i < 4; i++)
if (level == levels[i])
return i;
return -1;
}
// In main:
switch (getLevel(argv[1])) {
case 0: harl.debug(); // Fall through
case 1: harl.info(); // Fall through
case 2: harl.warning(); // Fall through
case 3: harl.error();
break;
default:
std::cout << "[ Probably complaining about insignificant problems ]" << std::endl;
}