This project has been created as part of the 42 curriculum by baelgadi.
Namespaces, classes, member functions, stdio streams, initialization lists, static, const, and some other basic stuff
You’ve spent months writing C. Malloc and free, pointers to pointers, structs passed by address, header files full of typedefs.
You understood the machine and you felt in control.
And now someone has handed you a new language and said: start over.
I understand the feeling… When I left Smallville, I had to build a new understanding of the world from scratch.
C is your Smallville. C++ is Metropolis: a larger world, with more structure, more people to protect, more ways things can go wrong if you’re not disciplined.
Let me show you around.
|
CPP Module 00:
3 exercises. C++98 standard only. Compiler: c++ · Flags: -Wall -Wextra -Werror · Must compile with -std=c++98Exercise 00: Megaphone · ex00/ · Makefile, megaphone.cppExercise 01: My Awesome PhoneBook · ex01/ · Makefile, .cpp, .hppExercise 02: The Job of Your Dreams · ex02/ · Makefile, Account.cpp, Account.hpp, tests.cpp · not mandatoryForbidden without exception: printf · *alloc · free · using namespace · friend · STL containers · STL algorithms |
My father Jor-El gave me knowledge. But knowledge without structure is just noise.
Before he could teach me anything useful, he had to give me a framework to hold it all.
That is what CPP00 does for C++.
You already know how to write a program. You know memory, you know pointers.
CPP00 won’t teach you to program again. But it will teach you a new way to organize what you build.
| Concept | Meaning |
|---|---|
| Namespaces | Separate territories that prevent naming conflicts |
| Classes | Structures that carry both data and the functions that operate on that data |
| Member functions | Functions that belong to a class and live inside it |
| Streams | C++’s Input/Output system. std::cout instead of printf |
| Static members | Data or functions that belong to the class itself, not to any individual object |
| const methods | Functions that promise not to alter the object they belong to |
The 3 exercises introduce these progressively: - Megaphone is five minutes of warm-up, essentially teaching you that C++ has its own way of doing things. And that way starts with streams and strings. - PhoneBook is the real lesson. Write your first 2 classes, understand public and private, manage state, handle I/O. - Account is a reading exercise as much as a writing one. You’re handed an interface and a log, and you deduce the implementation.
[!WARNING] Violating any of these rules ends the evaluation immediately.
| Violation | Grade |
|---|---|
printf, *alloc, free |
0 |
using namespace <name> |
-42 |
friend keyword |
-42 |
| STL containers or algorithms in the code | -42 |
| Function body implemented inside a header | 0 |
| No include guards | 0 |
UpperCamelCase.These rules exist because code lives longer than the person who writes it. The discipline you build now protects the people who read your code later.
In Metropolis, when I need to be heard across the city, I don’t whisper. I project clearly and without distortion.
Megaphone is that exercise:
Given words as command line arguments, you shout them in uppercase.
Given nothing, you broadcast the standard emergency signal.
The required behavior:
$ ./megaphone "shhhhh... I think the students are asleep..."
SHHHHH... I THINK THE STUDENTS ARE ASLEEP...
$ ./megaphone
* LOUD AND UNBEARABLE FEEDBACK NOISE *
The C++ way:
#include <iostream>
#include <cctype>
int main(int ac, char **av)
{
if (ac == 1)
{
std::cout << "* LOUD AND UNBEARABLE FEEDBACK NOISE *\n";
return (0);
}
for (int i = 1; i < ac; i++)
{
for (int j = 0; av[i][j] != '\0'; j++)
std::cout << (char)std::toupper((unsigned char)av[i][j]);
}
std::cout << std::endl;
return (0);
}[!NOTE] The cast to
(unsigned char)beforestd::toupperis not decoration.
On platforms wherecharis signed, characters with values above 127 would produce undefined behavior without it.
Do the right thing even when the compiler doesn’t force you to!
[!CAUTION] The return value of
std::toupperis int.
The(char)cast after the call converts it back so that it prints the character.
Without it, we would get the ASCII number instead.
This section explains every new concept CPP00 introduces, grounded in what you already know from C.
In C, name collisions were your problem to solve. You prefixed everything: ft_strlen, ft_split, … It worked, but it was a convention, not a language feature.
In C++, namespaces are built in:
namespace std
{
// the entire standard library lives here (cout, cin, string, ...)
}To use anything from the standard library, you prefix it with std::
std::cout << "hello" << std::endl;
std::string name = "Kal-El";[!WARNING] The rule
using namespace std;is forbidden in this module.
It also matters outside of 42:
In any large project, pulling an entire namespace into scope defeats the purpose of having namespaces at all.
You risk silent name conflicts, and you give up clarity about where each name comes from.
Here is the central metaphor of this entire module and it maps perfectly.
Clark Kent is the internal state. He has history and knows things no one else knows.
He is not hidden out of shame. Rather, he is protected because not everything should be exposed to everyone.
Superman is the public interface. When you call makeDeposit(), you are interacting with Superman.
The account adjustments, the logging, the internal counter increments… Those happen inside Clark Kent, invisible to you.
In C, you would write:
typedef struct s_account
{
int index;
int amount;
int nb_deposits;
} t_account;
void account_deposit(t_account *a, int amount)
{
a->amount += amount;
}Nothing stops anyone from reaching into t_account and setting amount to anything they want, therefore bypassing all logic.
In C++:
class Account {
public:
void makeDeposit(int deposit); // Superman
int checkAmount(void) const;
private:
int _amount; // Clark Kent
int _nbDeposits;
};Now _amount is inaccessible from outside the class.
The only way to change it is through makeDeposit, which applies all the correct logic.
[!NOTE] Anything that external code does not need to see is
private.
Anything that external code needs to call ispublic.
When in doubt, make it private.
| Specifier | Who can access |
|---|---|
public |
Any code, anywhere |
private |
Only methods of this class |
protected |
This class and its subclasses (seen in later modules) |
The difference between struct and class in C++ is exactly 1 thing:
struct members are public by default, class members are private by default.
In practice at 42, we use class for OOP objects.
In C, functions and data are separate. You pass a pointer to your struct and operate on it:
void contact_set_name(t_contact *c, const char *name)
{
ft_strlcpy(c->first_name, name, 50);
}In C++, the function lives inside the class and has direct access to the private members:
void Contact::setFirstName(std::string const &val)
{
_firstName = val; // directly accesses the private field
}The :: operator is the scope resolution operator.
Contact::setFirstName means “the setFirstName that belongs to Contact.”
[!NOTE] When you call a method, C++ automatically passes a hidden pointer to the current object. It’s called
this.
So_firstName = valis internallythis->_firstName = val.
When I arrive at a scene, things change. Resources are engaged and a presence is established.
When I leave, I make sure the situation is clean: no loose ends, no collateral left unaddressed.
That is basically what constructors and destructors do.
Constructor:called automatically when an object is created. Same name as the class, no return type.
Account::Account(int initial_deposit)
: _accountIndex(_nbAccounts), // initialization list
_amount(initial_deposit),
_nbDeposits(0),
_nbWithdrawals(0)
{
_nbAccounts++;
_totalAmount += initial_deposit;
_displayTimestamp();
std::cout << "index:" << _accountIndex << ";amount:" << _amount << ";created" << std::endl;
}The : member(value) syntax after the constructor signature is the initialization list.
It initializes members before the constructor body runs and it’s more efficient than assigning inside the body.
Destructor: called automatically when an object goes out of scope or is deleted. Same name as the class prefixed with ~.
Account::~Account(void)
{
_displayTimestamp();
std::cout << "index:" << _accountIndex << ";amount:" << _amount << ";closed" << std::endl;
}When a local variable reaches the end of its scope (the closing }), its destructor is called automatically.
No manual cleanup function!
The Fortress of Solitude does not belong to any particular moment in my life. It is not created fresh each time I arrive and destroyed when I leave.
It exists independently and is shared across every visit.
And static class members work the same way.
Static data member: 1 variable shared by all instances of the class.
Every Account object shares the same _nbAccounts. When the constructor increments it, every future Account sees the updated value.
// In Account.hpp (declaration)
private:
static int _nbAccounts;
// In Account.cpp (definition)
int Account::_nbAccounts = 0;[!WARNING] If you forget the definition in the
.cppfile, you get a linker error:undefined reference to `Account::_nbAccounts`The declaration in the header only says “this variable exists.” The definition in the
.cppsays “allocate the memory here.”
That’s why both lines are required.
Static method: a function that belongs to the class, not to any specific object. It has no this pointer and it can only access static members.
int Account::getNbAccounts(void)
{
return (_nbAccounts); // OK: static member
// return (_amount); // ERROR: _amount is per-instance, no 'this' here
}You call static methods on the class, not on an object: Account::getNbAccounts().
const methods: observing without interferingThere are moments when I am not there to act. Only to observe and understand.
I do not rearrange the scene. I do not change anything.
A const method makes that promise formally:
int Account::checkAmount(void) const
{
return (_amount); // read-only, fine
// _amount = 5; // compile error: can't modify in a const method
}The const after the closing parenthesis tells the compiler: “this method will not modify any member variables.”
If you try to, you get a compile error.
if a method doesn’t need to modify the object, make it const.
On Krypton, information was stored in crystalline matrices with their own access rules. Humans communicate much easier.
std::string is a translation layer. It takes the work of managing character data (allocation, reallocation, and all the shenanigans) and handles it for you.
| Task | C | C++ |
|---|---|---|
| Declaration | char name[50]; |
std::string name; |
| Assignment | ft_strlcpy(name, "Clark", 50) |
name = "Clark"; |
| Comparison | ft_strcmp(a, b) == 0 |
a == b |
| Length | ft_strlen(name) |
name.length() |
| Concatenation | ft_strcat(dest, src) |
dest += src; |
| Substring | manual loop | name.substr(0, 9) |
| Empty check | name[0] == '\0' |
name.empty() |
std::string manages its own memory so you never call malloc or free
When a std::string goes out of scope, its destructor releases the memory automatically.
[!NOTE] The subject says “no dynamic allocation” in Exercise 01.
This refers to theContact _contacts[8]array as it must live on the stack.
Usingstd::stringinside Contact is perfectly fine.
The Daily Planet runs on its printing press. Information goes in, gets formatted and shaped, and then it comes out in a form that people can easily read.
std::cout and std::cin are C++’s I/O system.
They replace printf and scanf entirely.
Output:
// C
printf("index: %d, amount: %d\n", index, amount);
// C++
std::cout << "index:" << index << ";amount:" << amount << std::endl;<< is the insertion operator.
It chains output operations left to right, like a pipeline
std::endl flushes the buffer and adds \n.
Input:
// Read one word (stop at whitespace)
std::string word;
std::cin >> word;
// Read a full line (including spaces)
std::string line;
std::getline(std::cin, line);[!IMPORTANT]
std::cin >> wordleaves the newline character in the buffer.
If you mix>>withgetline, the nextgetlinemay read an empty string (just that leftover newline).
So usegetlineconsistently, as we will do in the PhoneBook.
Checking for EOF:
if (!std::getline(std::cin, line))
break; // Ctrl+D pressed, stream endedWhen the stream ends (EOF), getline returns false.
In C, to pass data into a function without copying it, you passed a pointer:
void set_name(const char *name)
{
...
}C++ introduces references, aliases for existing variables:
void Contact::setFirstName(std::string const &val)
{
...
}std::string const &val means: “val is a reference to a const string, so I can read it but not modify it.”
-> or * to dereference. Syntax is identical to working with the originalconst & means “I’m reading, not writing”When to use const &: any time you pass an object into a function and don’t need to modify it.
It avoids an unnecessary copy of potentially large data.
The Fortress of Solitude has protocols.
Not everyone who arrives gets in and the same visitor doesn’t get processed twice.
Include guards prevent a header from being included more than once in the same compilation.
#ifndef CONTACT_HPP
# define CONTACT_HPP
// content here
#endifWithout guards: if 2 .cpp files include the same header, the compiler sees the class declared twice and fails with a redefinition error.
W/ guards: the first inclusion defines CONTACT_HPP. The second inclusion finds it already defined and skips the entire file.
[!NOTE]
#pragma oncedoes the same but in 1 line:#pragma once // content hereThe preprocessor marks the file itself. If it’s included again, the whole file is skipped.
It’s not part of the C++ standard but supported by every major compiler (GCC, Clang included)
It’s also provides slightly faster compilation in some implementations since the compiler can skip the file before even opening it.
Metropolis has standards: the buildings are aligned, the streets are measured, the signal is broadcast at just the right frequency.
The <iomanip> header provides the tools to format output with that kind of precision:
| Manipulator | Effect | Example | Output |
|---|---|---|---|
std::setw(n) |
Sets minimum field width to n for the next output only |
std::setw(6) << "hi" |
‎ ‎ ‎ ‎ hi |
std::setfill(c) |
Sets the fill character (default: space) | std::setfill('-') << std::setw(6) << "hi" |
----hi |
std::right |
Right aligns text within the field width | std::setw(6) << std::right << "hi" |
‎ ‎ ‎ ‎ hi |
std::left |
Left aligns text within the field width | std::setw(6) << std::left << "hi" |
hi‎ ‎ ‎ ‎ |
[!NOTE]
std::setwonly affects the next output operation, then resets automatically.
std::setfillpersists.
If we setsetfill('0')for a timestamp and don’t reset it, the nextsetwin SEARCH might pad with0instead of spaces.
cd ex00
make
./megaphone
./megaphone "shhhhh... I think the students are asleep..."
./megaphone a b ccd ex01
make
# Empty search
echo -e "SEARCH\nEXIT" | ./phonebook
# Basic add and search
printf "ADD\nLois\nLane\nSuperwoman\n555-1938\nClark’s glasses aren’t fooling anyone\nSEARCH\n0\nEXIT\n" | ./phonebook
# Truncation test (first name > 10 chars)
printf "ADD\nDoomsdayDestroyerOfWorlds\nBloome\nDavis\n555-1992\nKilled Superman once\nSEARCH\n0\nEXIT\n" | ./phonebook
# Invalid indexes
printf "ADD\nLois\nLane\nSuperwoman\n555-1938\nClark’s glasses aren’t fooling anyone\nSEARCH\n99\nEXIT\n" | ./phonebook
printf "ADD\nJimmy\nOlsen\nMy pal\n555-1954\nOwns a signal watch for emergencies\nSEARCH\nabc\nEXIT\n" | ./phonebook
# Empty field rejection (reprompt for first name)
printf "ADD\n\nLois\nLane\nSuperwoman\n555-1938\nClark’s glasses aren’t fooling anyone\nEXIT\n" | ./phonebook
# Circular replacement: add 9, check that index 0 holds the 9th
# (9th contact replaces the first)
printf "ADD\nLana\nLang\nInsect Queen\n555-1946\nShe wore the suit so Lex could not\nADD\nLois\nLane\nSuperwoman\n555-1938\nClark's glasses aren't fooling anyone\nADD\nJimmy\nOlsen\nMy pal\n555-1954\nOwns a signal watch for emergencies\nADD\nLex\nLuthor\nGenius\n555-1941\nObsessed with kryptonite\nADD\nPerry\nWhite\nEditor\n555-1942\nGreat Caesar ghost\nADD\nVril\nDox\nBrainiac\n555-1943\nShrinks cities for fun\nADD\nGeneral\nZod\nKneel\n555-1944\nBanished from Krypton\nADD\nJonathan\nKent\nPa Kent\n555-1945\nTaught Clark to hide powers\nSEARCH\n0\nADD\nKara\nZor-El\nSupergirl\n555-1984\nActually stronger than her cousin\nSEARCH\n0\nEXIT\n" | ./phonebook
# EOF clean exit
echo -n "" | ./phonebook
make recd ex02
make
# Compare output structure with 19920104_091532.log (ignoring timestamps)
./tests
# Strip timestamps from both and diff them
# The last 8 lines (destructors) may differ but everything else must match
./tests | sed 's/\[.*\]/[TS]/g' > /tmp/mine.txt
sed 's/\[.*\]/[TS]/g' ./19920104_091532.log > /tmp/ref.txt
diff /tmp/mine.txt /tmp/ref.txt
make reThe transition from C to C++ is not asking you to abandon what you learned. It is requiring that you organize it better.
While C taught you how the machine works, C++ teaches you how to build systems that other people can understand and trust.
You already know how to write code that works. Now go write code that holds.
Somewhere, in our darkest night, we made up the story of a language that would never let us down…