Skip to content

Module 00: C++ Fundamentals

Download Official Subject PDF

Key Concepts:

  • Namespaces
  • Classes and Objects
  • Member Functions (methods)
  • Access Specifiers (public/private)
  • Constructors and Destructors
  • iostream (cout, cin, cerr)
  • std::string
  • Initialization Lists
  • static and const keywords

Understanding C++ Operators (For Complete Beginners)

Section titled “Understanding C++ Operators (For Complete Beginners)”

Before diving into C++ code, let’s understand the operators you’ll see throughout this module:

std::cout << "Hello" << 42 << std::endl;
  • << is called the “stream insertion operator” or “output operator”
  • It inserts data into an output stream (like std::cout, which stands for “character output”)
  • Think of it as an arrow pointing in the direction of data flow: data flows FROM the variables TO the output
  • You can chain multiple << operators together to output multiple values
  • std::endl inserts a newline and flushes the buffer
std::cin >> number;
  • >> is called the “stream extraction operator” or “input operator”
  • It extracts data from an input stream (like std::cin, which stands for “character input”)
  • Think of it as an arrow pointing in the direction of data flow: data flows FROM the input TO the variable
std::cout // Access cout from the std namespace
ClassName::method // Access method from ClassName
  • :: is the “scope resolution operator”
  • It tells the compiler WHERE to look for a name (variable, function, class)
  • std::cout means “use the cout that belongs to the std namespace”
  • This prevents naming conflicts when different libraries use the same names
object.method() // Access method of an object
object.attribute // Access attribute of an object
  • . is the “member access operator” or “dot operator”
  • It accesses members (methods or attributes) of an object
  • Use this when you have an actual object (not a pointer)
pointer->method() // Same as (*pointer).method()
pointer->attribute // Same as (*pointer).attribute
  • -> is the “arrow operator” or “pointer member access operator”
  • It’s a shortcut for dereferencing a pointer AND accessing a member
  • pointer->method() is exactly the same as (*pointer).method()
  • Use this when you have a pointer to an object
int* ptr = &x; // ptr stores the memory address of x
  • & before a variable gives you its memory address
  • This is how you get a pointer to a variable
  • Don’t confuse this with & in declarations (which creates a reference)
*ptr = 42; // Store 42 at the address ptr points to
int value = *ptr; // Get the value stored at ptr
  • * before a pointer “dereferences” it (accesses the value at that address)
  • It follows the pointer to the actual data stored in memory

CC++
printf()std::cout <<
scanf()std::cin >>
malloc()/free()new/delete
struct (data only)class (data + behavior)
Functions operate on dataObjects have methods
// FORBIDDEN - will get you 0
printf("Hello"); // Use std::cout instead
malloc(sizeof(int)); // Use new instead
free(ptr); // Use delete instead
// FORBIDDEN - will get you -42
using namespace std; // Must prefix with std::

Namespaces solve two problems:

1. Name collisions: In large projects, two libraries might define a function with the same name.

// Without namespaces - collision!
void print(); // Library A
void print(); // Library B - ERROR!
// With namespaces - no collision
namespace LibraryA {
void print();
}
namespace LibraryB {
void print();
}
// Usage
LibraryA::print();
LibraryB::print();

2. Code organization: Namespaces let you group related symbols semantically across multiple files. Unlike C where organization is file-based, namespaces let you organize by meaning. A namespace can span many files, and related functions stay together logically even when physically separated.

Everything from the C++ standard library lives in std:

std::cout // output stream
std::cin // input stream
std::cerr // error stream
std::string // string class
std::endl // newline + flush
std::cout // cout from std namespace
::globalFunction() // function from global namespace (no namespace)
ClassName::method // method from ClassName

#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
int x = 42;
std::cout << "The answer is " << x << std::endl;
// Chaining multiple values
std::cout << "a=" << 1 << ", b=" << 2 << std::endl;
return 0;
}
#include <iostream>
#include <string>
int main() {
int number;
std::cout << "Enter a number: ";
std::cin >> number;
std::string name;
std::cout << "Enter your name: ";
std::cin >> name; // Only reads until whitespace!
// For full line input:
std::getline(std::cin, name);
return 0;
}
// PROBLEM: mixing cin >> and getline
int age;
std::string name;
std::cin >> age; // Leaves '\n' in buffer
std::getline(std::cin, name); // Reads empty line!
// SOLUTION: clear the buffer
std::cin >> age;
std::cin.ignore(); // Ignore the leftover '\n'
std::getline(std::cin, name);
#include <iostream>
#include <iomanip>
int main() {
// Set field width
std::cout << std::setw(10) << "Hello" << std::endl; // " Hello"
// Right/left alignment
std::cout << std::right << std::setw(10) << "Hi" << std::endl; // " Hi"
std::cout << std::left << std::setw(10) << "Hi" << std::endl; // "Hi "
// Fill character
std::cout << std::setfill('.') << std::setw(10) << "Hi" << std::endl; // "........Hi"
return 0;
}

// C-style (dangerous, manual memory)
char* str = (char*)malloc(100);
strcpy(str, "Hello");
// Must remember to free!
// C++ style (safe, automatic)
std::string str = "Hello";
// Memory managed automatically
#include <string>
std::string s = "Hello";
// Length
s.length(); // 5
s.size(); // 5 (same thing)
// Access characters
s[0]; // 'H'
s.at(0); // 'H' (with bounds checking)
// Concatenation
s + " World"; // "Hello World"
s.append(" World"); // Modifies s
// Comparison
s == "Hello"; // true
s < "World"; // true (lexicographic)
// Substrings
s.substr(0, 3); // "Hel"
s.substr(2); // "llo"
// Find
s.find("ll"); // 2 (index)
s.find("xyz"); // std::string::npos (not found)
// Replace (but remember: forbidden in ex04!)
s.replace(0, 2, "YY"); // "YYllo"
// Clear
s.empty(); // false
s.clear(); // s is now ""
std::string s = "Hello";
// Index-based
for (size_t i = 0; i < s.length(); i++) {
std::cout << s[i];
}
// C++98 doesn't have range-based for loops!
// This is C++11: for (char c : s) { } // FORBIDDEN

In C++, both class and struct can have member variables and functions. The only difference is the default access specifier:

Featureclassstruct
Default accessprivatepublic
Default inheritanceprivatepublic
Members accessible?Only inside classFrom anywhere

Practical Example:

class MyClass {
int x; // PRIVATE by default - only accessible inside class
void foo(); // PRIVATE by default
};
struct MyStruct {
int x; // PUBLIC by default - accessible from anywhere
void foo(); // PUBLIC by default
};
MyClass c;
c.x = 5; // ERROR: x is private!
MyStruct s;
s.x = 5; // OK: x is public

When to use each:

  • Use class when you need encapsulation (hide implementation)
  • Use struct for simple data structures (like C structs)
  • In practice, both work the same - it’s about convention

Visual Memory Layout:

MyClass object: MyStruct object:
+-------------+ +-------------+
| _firstName | | _firstName | <-- All members
| _lastName | | _lastName | are accessible
| _phone | | _phone |
+-------------+ +-------------+
(private) (public)
Contact.hpp
#ifndef CONTACT_HPP
#define CONTACT_HPP
#include <string>
class Contact {
private:
// Attributes (data members)
std::string _firstName;
std::string _lastName;
std::string _phoneNumber;
public:
// Constructor
Contact();
// Destructor
~Contact();
// Member functions (methods)
void setFirstName(std::string name);
std::string getFirstName() const;
void display() const;
};
#endif
Contact.cpp
#include "Contact.hpp"
#include <iostream>
// Constructor implementation
Contact::Contact() {
std::cout << "Contact created" << std::endl;
}
// Destructor implementation
Contact::~Contact() {
std::cout << "Contact destroyed" << std::endl;
}
// Setter
void Contact::setFirstName(std::string name) {
this->_firstName = name;
}
// Getter (const - doesn't modify object)
std::string Contact::getFirstName() const {
return this->_firstName;
}
// Display method
void Contact::display() const {
std::cout << "Name: " << _firstName << " " << _lastName << std::endl;
}
SpecifierAccess
privateOnly accessible within the class
publicAccessible from anywhere
protectedAccessible in class and derived classes

Rule of thumb: Make attributes private, provide public getters/setters.

Every non-static member function has access to a special pointer called this. It points to the current instance - the object on which the method was called. This is how member functions know which object’s data to access.

class Example {
private:
int value;
public:
void setValue(int value) {
// 'value' refers to parameter
// 'this->value' refers to member
this->value = value;
}
// this is implicit - these two are equivalent:
int getValue() { return value; }
int getValue() { return this->value; }
};

Why this matters: When you call obj.setValue(42), the compiler passes &obj as a hidden first argument. Inside the function, this == &obj.

class Example {
private:
int _value;
public:
// Can modify object
void setValue(int v) { _value = v; }
// Cannot modify object (const at the end)
int getValue() const { return _value; }
};

class MyClass {
public:
MyClass() {
std::cout << "Default constructor called" << std::endl;
}
};
// Usage
MyClass obj; // Calls default constructor
class Contact {
private:
std::string _name;
int _age;
public:
Contact(std::string name, int age) {
_name = name;
_age = age;
}
};
// Usage
Contact c("John", 25);
class Contact {
private:
std::string _name;
int _age;
public:
// WITHOUT initialization list (assignment in body)
Contact(std::string name, int age) {
_name = name; // First default-constructed, then assigned
_age = age;
}
// WITH initialization list (direct initialization)
Contact(std::string name, int age) : _name(name), _age(age) {
// Members initialized before body executes
}
};

Why use initialization lists?

  1. More efficient: Without init list, members are default-constructed THEN assigned (2 operations). With init list, members are directly initialized (1 operation).

    // WITHOUT init list - TWO operations per member:
    Contact(std::string name) {
    // 1. _name is default-constructed (empty string)
    // 2. _name is assigned the value of 'name'
    _name = name;
    }
    // WITH init list - ONE operation per member:
    Contact(std::string name) : _name(name) {
    // _name is directly constructed with 'name'
    }
  2. Required for const members (can’t assign to const after construction)

  3. Required for reference members (must be bound at initialization)

  4. Required for members without default constructors

class Example {
private:
const int _id; // MUST use init list
std::string& _ref; // MUST use init list
public:
// This is the ONLY way:
Example(int id, std::string& ref) : _id(id), _ref(ref) {}
};
class FileHandler {
private:
int* _data;
public:
FileHandler() {
_data = new int[100]; // Allocate
}
~FileHandler() {
delete[] _data; // Clean up
std::cout << "FileHandler destroyed, memory freed" << std::endl;
}
};

Shared across ALL instances of a class.

// Header
class Counter {
private:
static int _count; // Declaration
public:
Counter() { _count++; }
~Counter() { _count--; }
static int getCount() { return _count; }
};
// Source (MUST define outside class)
int Counter::_count = 0; // Definition + initialization
// Usage
Counter a;
Counter b;
Counter c;
std::cout << Counter::getCount(); // 3

Can be called without an object. Cannot access non-static members.

class Math {
public:
static int add(int a, int b) {
return a + b;
}
};
// Usage - no object needed
int result = Math::add(5, 3);

Design Philosophy: const is more than a language feature - it’s a discipline for writing correct code. By marking everything that shouldn’t change as const, you:

  • Catch bugs at compile time (accidental modifications)
  • Document intent (readers know what won’t change)
  • Enable compiler optimizations
  • Make code easier to reason about in complex systems

Rule of thumb: Use const by default. Only remove it when you need to modify something.

const int MAX = 100; // Cannot be modified
MAX = 200; // ERROR!
void print(const std::string& s) {
// s cannot be modified
// Passed by reference (efficient, no copy)
std::cout << s << std::endl;
}
class Example {
public:
int getValue() const { // Promises not to modify object
return _value;
}
};
class Example {
private:
std::string _name;
public:
// Returns const reference - caller cannot modify
const std::string& getName() const {
return _name;
}
};

Every header file MUST have include guards to prevent double inclusion:

Contact.hpp
#ifndef CONTACT_HPP
#define CONTACT_HPP
class Contact {
// ...
};
#endif

Why?

// Without guards:
#include "Contact.hpp"
#include "Contact.hpp" // ERROR: Contact redefined!
// With guards:
#include "Contact.hpp" // Defines CONTACT_HPP, includes class
#include "Contact.hpp" // CONTACT_HPP already defined, skipped

NAME = program
CXX = c++
CXXFLAGS = -Wall -Wextra -Werror -std=c++98
SRCS = main.cpp Contact.cpp PhoneBook.cpp
OBJS = $(SRCS:.cpp=.o)
all: $(NAME)
$(NAME): $(OBJS)
$(CXX) $(CXXFLAGS) -o $(NAME) $(OBJS)
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -f $(OBJS)
fclean: clean
rm -f $(NAME)
re: fclean all
.PHONY: all clean fclean re

TaskCC++
Printprintf("x=%d\n", x);std::cout << "x=" << x << std::endl;
Read intscanf("%d", &x);std::cin >> x;
Read linefgets(buf, size, stdin);std::getline(std::cin, str);
Allocatemalloc(n * sizeof(int))new int[n]
Freefree(ptr)delete[] ptr
Stringchar str[100]std::string str
String lengthstrlen(str)str.length()
String copystrcpy(dst, src)dst = src
String comparestrcmp(a, b) == 0a == b

When reading input in a loop, you need to handle EOF (Ctrl+D on Unix, Ctrl+Z on Windows):

std::string line;
// std::getline returns the stream, which converts to false on EOF
while (std::getline(std::cin, line)) {
// Process line
std::cout << "Got: " << line << std::endl;
}
// Loop exits when EOF is reached
// You can also check explicitly:
if (std::cin.eof()) {
std::cout << "End of input reached" << std::endl;
}
#include <cstdlib> // for std::exit
std::string input;
std::cout << "Enter value: ";
if (!std::getline(std::cin, input)) {
std::cout << std::endl; // Print newline for clean output
std::exit(0); // Exit program gracefully
}

When you need a fixed-size collection where new items replace the oldest:

class PhoneBook {
private:
Contact _contacts[8]; // Fixed size array
int _index; // Next position to write
int _count; // Total contacts stored
public:
PhoneBook() : _index(0), _count(0) {}
void addContact(const Contact& c) {
_contacts[_index] = c;
// Wrap around using modulo
_index = (_index + 1) % 8; // 0,1,2,3,4,5,6,7,0,1,2...
// Track count up to max
if (_count < 8)
_count++;
}
};
int index = 0;
int size = 8;
index = (index + 1) % size; // 0 -> 1
index = (index + 1) % size; // 1 -> 2
// ... after 7:
index = (index + 1) % size; // 7 -> 0 (wraps around!)

C++ extends the pointer concept to allow pointers to class members (both attributes and member functions). This is different from regular pointers.

class Sample {
public:
int value;
};
// Pointer to member attribute
int Sample::*ptr = &Sample::value;
// Usage - need an instance to dereference
Sample s;
s.value = 42;
std::cout << s.*ptr << std::endl; // Prints 42
Sample* sp = &s;
std::cout << sp->*ptr << std::endl; // Also prints 42
class Sample {
public:
void display() { std::cout << "Hello" << std::endl; }
void greet(std::string name) { std::cout << "Hi " << name << std::endl; }
};
// Pointer to member function (no parameters)
void (Sample::*funcPtr)() = &Sample::display;
// Usage
Sample s;
(s.*funcPtr)(); // Calls s.display()
Sample* sp = &s;
(sp->*funcPtr)(); // Also calls display()
// Pointer to member function with parameters
void (Sample::*greetPtr)(std::string) = &Sample::greet;
(s.*greetPtr)("World"); // Calls s.greet("World")
  • Callbacks: Select which member function to call at runtime
  • Data-driven design: Access different attributes based on configuration
  • Generic algorithms: Write functions that work on any member

  1. Using using namespace std; - Forbidden at 42
  2. Forgetting include guards - Causes compilation errors
  3. Putting implementation in headers - Grade 0 (except templates)
  4. Not ending output with newline - Required by subject
  5. Using printf/scanf - Forbidden at 42
  6. Forgetting const on getters - Bad practice
  7. Not initializing members - Undefined behavior
  8. Memory leaks - Always pair new with delete