1. Why C++?

C++ is one of the most powerful and widely used programming languages in the world. It gives you direct control over hardware and memory while still supporting high-level abstractions like classes, templates, and the STL.

Where C++ Is Used

  • Game engines: Unreal Engine, Unity's core, virtually every AAA game
  • Operating systems: Windows, Linux kernel modules, macOS components
  • Browsers: Chrome, Firefox, Safari are all written in C++
  • Embedded systems: Robotics, IoT devices, automotive software
  • Finance: High-frequency trading systems where microseconds matter
  • Competitive programming: Most competitive programmers use C++ for speed
  • Databases: MySQL, MongoDB, Redis

C++ vs Other Languages

FeatureC++PythonJava
SpeedExtremely fastSlow (interpreted)Fast (JIT compiled)
Memory controlFull manual controlGarbage collectedGarbage collected
Learning curveSteepGentleModerate
Use caseSystems, games, perf-criticalScripting, ML, webEnterprise, Android

How C++ Code Runs

Unlike Python (interpreted line by line), C++ is compiled. Your code goes through several steps:

Source code (.cpp) --> Preprocessor --> Compiler --> Object file (.o) --> Linker --> Executable

You write .cpp files, the compiler (like g++) turns them into machine code, and the linker combines everything into an executable you can run.

Key Mindset Shift

In Python, you think about what to do. In C++, you also think about how memory works. This is harder but gives you much more power and understanding of how computers actually work. Every concept in this page builds toward that understanding.

2. Hello World and Basic Syntax

C++
// Every C++ program starts with #include and main()
#include <iostream>    // For input/output

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;  // 0 means "everything went fine"
}

Breaking It Down

  • #include <iostream> -- imports the input/output library (like import in Python)
  • int main() -- the entry point. Every C++ program must have exactly one main function
  • std::cout -- "character output" -- prints to the console. The << operator sends data to it
  • std::endl -- prints a newline and flushes the buffer (you can also use "\n")
  • return 0; -- tells the OS the program succeeded
  • Every statement ends with a semicolon ;

Compiling and Running

Terminal
# Compile
g++ -o hello hello.cpp

# Run
./hello

Reading Input

C++
#include <iostream>

int main() {
    int age;
    std::cout << "Enter your age: ";
    std::cin >> age;   // Read from keyboard
    std::cout << "You are " << age << " years old." << std::endl;
    return 0;
}
Using Namespace

Tired of typing std:: everywhere? Add using namespace std; after your includes. This lets you write cout instead of std::cout. It's fine for learning and competitive programming, but avoided in production code because it can cause name conflicts.

3. Data Types

In C++, every variable has a type that determines how much memory it uses and what values it can hold. Unlike Python, you must declare the type explicitly (or use auto).

Primitive Types

TypeSize (typical)RangeExample
bool1 bytetrue / falsebool alive = true;
char1 byte-128 to 127 (or a character)char grade = 'A';
int4 bytes-2.1 billion to 2.1 billionint count = 42;
long long8 bytes-9.2 quintillion to 9.2 quintillionlong long big = 1e18;
float4 bytes~7 decimal digits precisionfloat pi = 3.14f;
double8 bytes~15 decimal digits precisiondouble pi = 3.14159265;
void0No value (used for functions)void doStuff();

Signed vs Unsigned

By default, integers are signed (can be negative). Adding unsigned makes them non-negative but doubles the positive range:

int: -2,147,483,648 to 2,147,483,647
unsigned int: 0 to 4,294,967,295

Integer Overflow

Danger: Overflow

When a value exceeds its type's range, it wraps around. This causes bugs that are incredibly hard to find:

int x = 2147483647;  // Max int value
x = x + 1;             // OVERFLOW! x becomes -2147483648

unsigned int y = 0;
y = y - 1;              // UNDERFLOW! y becomes 4294967295

Use long long when numbers might exceed ~2 billion. In competitive programming, always use long long to be safe.

Type Casting

C++
// Implicit (automatic) -- compiler does it for you
int a = 5;
double b = a;        // b is 5.0 (safe, no data loss)

// Implicit (dangerous) -- data loss!
double c = 3.99;
int d = c;           // d is 3 (decimal part is LOST, not rounded)

// Explicit -- use static_cast to be clear about your intent
double e = 9.7;
int f = static_cast<int>(e);  // f is 9 (explicit, readable)

auto Keyword

Let the compiler figure out the type for you:

C++
auto x = 42;          // int
auto y = 3.14;        // double
auto name = std::string("Sean");  // std::string

const and constexpr

C++
const int MAX_SIZE = 100;       // Cannot be changed after initialization
constexpr int SQUARE = 5 * 5;  // Computed at COMPILE time (even faster)

const means "don't change this." constexpr means "compute this at compile time, not runtime." Use constexpr whenever the value is truly known at compile time.

4. Variables and Operators

Declaration and Initialization

C++
int x;             // Declaration (DANGER: x has garbage value!)
int y = 10;        // C-style initialization
int z{10};         // Brace initialization (preferred in modern C++)
int w(10);         // Constructor-style initialization
Always Initialize Variables

In C++, uninitialized variables contain garbage -- whatever happened to be in that memory location. This is a common source of bugs. Always give variables a starting value.

Arithmetic Operators

OperatorMeaningExampleResult
+Addition5 + 38
-Subtraction5 - 32
*Multiplication5 * 315
/Division7 / 23 (integer division!)
%Modulus (remainder)7 % 21
++Incrementx++x = x + 1
--Decrementx--x = x - 1
Integer Division Trap

7 / 2 gives 3, not 3.5! When both operands are integers, C++ does integer division (truncates). To get 3.5, make at least one operand a double: 7.0 / 2 or static_cast<double>(7) / 2.

Comparison and Logical Operators

OperatorMeaningExample
==Equal tox == 5
!=Not equal tox != 5
<, >Less/greater thanx < 10
<=, >=Less/greater or equalx >= 0
&&Logical ANDx > 0 && x < 10
||Logical ORx == 0 || x == 1
!Logical NOT!done

Bitwise Operators

OperatorMeaningExample
&AND5 & 3 = 1
|OR5 | 3 = 7
^XOR5 ^ 3 = 6
~NOT (flip bits)~5
<<Left shift1 << 3 = 8
>>Right shift8 >> 2 = 2
Bit Shift Trick

1 << n is the fastest way to compute 2n. And x >> 1 is the fastest way to divide by 2. You'll see these everywhere in competitive programming and systems code.

5. Control Flow

if / else if / else

C++
int score = 85;

if (score >= 90) {
    std::cout << "A";
} else if (score >= 80) {
    std::cout << "B";
} else if (score >= 70) {
    std::cout << "C";
} else {
    std::cout << "F";
}

Ternary Operator

A one-line if/else:

C++
int age = 20;
std::string status = (age >= 18) ? "adult" : "minor";

switch Statement

C++
char grade = 'B';
switch (grade) {
    case 'A': std::cout << "Excellent"; break;
    case 'B': std::cout << "Good";      break;
    case 'C': std::cout << "Average";   break;
    default:  std::cout << "Invalid";   break;
}
Don't Forget break!

Without break, execution "falls through" to the next case. This is a classic C++ bug. Every case should end with break unless you intentionally want fall-through.

for Loop

C++
// Classic for loop
for (int i = 0; i < 5; i++) {
    std::cout << i << " ";  // 0 1 2 3 4
}

// Range-based for loop (modern C++)
std::vector<int> nums = {10, 20, 30};
for (int n : nums) {
    std::cout << n << " ";  // 10 20 30
}

while and do-while

C++
// while -- checks condition BEFORE each iteration
int i = 0;
while (i < 5) {
    std::cout << i << " ";
    i++;
}

// do-while -- runs at least ONCE, checks condition AFTER
int input;
do {
    std::cout << "Enter a positive number: ";
    std::cin >> input;
} while (input <= 0);

break and continue

C++
for (int i = 0; i < 10; i++) {
    if (i == 5) break;      // Exit the loop entirely
    if (i % 2 == 0) continue; // Skip to next iteration
    std::cout << i << " ";    // Prints: 1 3
}

6. Functions

Basic Syntax

C++
// Declaration (prototype) -- tells compiler this function exists
int add(int a, int b);

// Definition -- the actual implementation
int add(int a, int b) {
    return a + b;
}

// Usage
int result = add(3, 4);  // result = 7

Pass by Value vs Pass by Reference

This is one of the most important concepts in C++:

C++
// Pass by VALUE -- function gets a COPY
void doubleIt(int x) {
    x = x * 2;  // Only changes the local copy!
}

int num = 5;
doubleIt(num);
// num is STILL 5! The function only modified its own copy.

// Pass by REFERENCE -- function gets the ORIGINAL variable
void doubleIt(int& x) {  // Note the &
    x = x * 2;  // Changes the original!
}

int num = 5;
doubleIt(num);
// num is now 10!
When to Use Which

Pass by value: When the function shouldn't modify the original (small types like int, double).

Pass by reference: When you want to modify the original, or when copying is expensive (large objects).

Pass by const reference: When you don't want to modify but also don't want to copy: void print(const string& s). This is the go-to for strings, vectors, and any large object.

Function Overloading

C++ lets you have multiple functions with the same name but different parameter types:

C++
int    add(int a, int b)       { return a + b; }
double add(double a, double b) { return a + b; }

add(3, 4);       // Calls int version, returns 7
add(3.0, 4.0);   // Calls double version, returns 7.0

Default Arguments

C++
void greet(std::string name = "World") {
    std::cout << "Hello, " << name << "!" << std::endl;
}

greet("Sean");   // Hello, Sean!
greet();          // Hello, World!

7. Pointers (The Big One)

Pointers are C++'s most powerful and most feared feature. But they're not actually that hard once you understand the mental model.

What Is a Pointer?

Think of computer memory as a long street of houses. Each house has an address (a number) and a value (what's inside). A pointer is a variable that stores the address of another variable -- like writing down someone's house number on a piece of paper.

C++
int x = 42;        // A regular variable (a house with 42 inside)
int* ptr = &x;     // A pointer to x (stores x's address)

std::cout << x;      // 42 (the value)
std::cout << &x;     // 0x7fff5a2 (x's memory address)
std::cout << ptr;    // 0x7fff5a2 (same address -- ptr stores it)
std::cout << *ptr;   // 42 (dereference -- "go to the address and read the value")
&x = "address of x" (give me the house number)
*ptr = "value at address stored in ptr" (go to that house and look inside)
int* = "pointer to int" (a variable that holds an address of an int)

Memory Diagram

Stack Memory: +----------+---------+------------+ | Variable | Value | Address | +----------+---------+------------+ | x | 42 | 0x1000 | | ptr | 0x1000 | 0x1008 | <-- ptr stores x's address +----------+---------+------------+ *ptr means: "go to address 0x1000 and read what's there" --> 42

Modifying Through a Pointer

C++
int x = 42;
int* ptr = &x;

*ptr = 100;        // Change the value AT the address ptr points to
std::cout << x;    // 100 -- x changed because ptr pointed at it!

nullptr

A pointer that points to "nothing" -- always initialize pointers to nullptr if you don't have an address yet:

C++
int* ptr = nullptr;  // Points to nothing (safe)
// *ptr would CRASH (segfault) -- never dereference nullptr!

if (ptr != nullptr) {
    std::cout << *ptr;  // Only dereference if not null
}

Pointer Arithmetic

Adding to a pointer moves it forward by the size of the type it points to:

C++
int arr[] = {10, 20, 30, 40};
int* p = arr;       // Points to arr[0]

std::cout << *p;       // 10
std::cout << *(p+1);   // 20 (next int, 4 bytes forward)
std::cout << *(p+2);   // 30

Pointer to Pointer (int**)

A pointer can also point to another pointer. Think of it as: "I have a note with an address on it, and at that address there's another note with another address."

C++
int x = 42;
int* p = &x;       // p points to x
int** pp = &p;     // pp points to p (pointer to pointer)

std::cout << x;     // 42
std::cout << *p;    // 42 (dereference once: go to x)
std::cout << **pp;  // 42 (dereference twice: go to p, then go to x)
+----------+---------+------------+ | Variable | Value | Address | +----------+---------+------------+ | x | 42 | 0x1000 | | p | 0x1000 | 0x1008 | <-- p stores &x | pp | 0x1008 | 0x1010 | <-- pp stores &p +----------+---------+------------+ pp --> p --> x **pp: go to pp(0x1008), read p(0x1000), go there, read 42

Pointer to Pointer to Pointer (int***)

Yes, you can go deeper. Three levels of indirection:

C++
int x = 42;
int* p = &x;        // Level 1: points to x
int** pp = &p;      // Level 2: points to p
int*** ppp = &pp;   // Level 3: points to pp

std::cout << ***ppp;   // 42 (follow three arrows to reach x)
+----------+---------+------------+ | Variable | Value | Address | +----------+---------+------------+ | x | 42 | 0x1000 | | p | 0x1000 | 0x1008 | | pp | 0x1008 | 0x1010 | | ppp | 0x1010 | 0x1018 | +----------+---------+------------+ ppp --> pp --> p --> x ***ppp = 42
When Would You Actually Use Multi-Level Pointers?
  • int** : Very common -- used for 2D arrays, arrays of strings in C, and functions that need to modify a pointer
  • int*** : Rare in practice -- sometimes 3D arrays or complex data structures. If you find yourself here, consider using vectors or classes instead
  • int**** : Almost never. If you need this, your design probably needs rethinking

Common Pointer Pitfalls

Dangers to Watch For
  • Dangling pointer: Points to memory that's been freed. Dereferencing it is undefined behavior (crash or garbage).
  • Wild pointer: Never initialized. Contains a random address. Always initialize to nullptr.
  • Memory leak: You new something but never delete it. The memory is gone until the program ends.
  • Double free: Deleting the same memory twice. Causes crashes.

Modern C++ largely solves these with smart pointers (Section 13).

8. References

A reference is an alias (another name) for an existing variable. Unlike a pointer, it can't be null and can't be reassigned.

C++
int x = 42;
int& ref = x;    // ref IS x (another name for the same memory)

ref = 100;
std::cout << x;  // 100 -- changing ref changes x

References vs Pointers

FeatureReferencePointer
Syntaxint& ref = x;int* ptr = &x;
Can be null?NoYes (nullptr)
Can be reassigned?No (always refers to same variable)Yes (can point to different things)
Must be initialized?YesNo (but should be)
Dereference needed?No (use directly)Yes (*ptr)
Rule of Thumb

Use references when you can, pointers when you must. References are safer and cleaner. Use pointers when you need nullptr, pointer arithmetic, or dynamic memory.

9. Arrays

C-Style Arrays

C++
int arr[5] = {10, 20, 30, 40, 50};

std::cout << arr[0];    // 10 (0-indexed!)
std::cout << arr[4];    // 50
// arr[5] -- OUT OF BOUNDS! No error, just undefined behavior (scary)
No Bounds Checking

C-style arrays do NOT check if your index is valid. arr[100] won't give an error -- it'll just read random memory. This is a major source of security vulnerabilities. Prefer std::vector or std::array instead.

std::array (Modern C++)

C++
#include <array>

std::array<int, 5> arr = {10, 20, 30, 40, 50};
std::cout << arr.size();    // 5 (knows its own size!)
std::cout << arr.at(2);     // 30 (bounds-checked -- throws exception if out of range)

Array Decay to Pointer

When you pass a C-style array to a function, it "decays" into a pointer to its first element and loses its size information:

C++
void printArray(int* arr, int size) {
    for (int i = 0; i < size; i++)
        std::cout << arr[i] << " ";
}
// You must pass size separately -- the array forgets it!

This is one more reason to prefer std::vector.

10. Strings

C-Strings (The Old Way)

A C-string is just a char array ending with a null character '\0':

C++
char name[] = "Hello";  // Actually stores: H e l l o \0
// Dangerous: no bounds checking, must manage \0 yourself
Avoid C-Strings

C-strings are error-prone (buffer overflows, missing null terminators). Use std::string for everything unless you're interfacing with C code.

std::string (The Right Way)

C++
#include <string>

std::string s1 = "Hello";
std::string s2 = "World";

// Concatenation
std::string s3 = s1 + " " + s2;  // "Hello World"

// Length
std::cout << s3.size();      // 11
std::cout << s3.length();    // 11 (same thing)

// Access characters
std::cout << s3[0];          // 'H'
std::cout << s3.at(4);       // 'o' (bounds-checked)

// Comparison (works with == unlike C!)
if (s1 == "Hello") { /* true */ }

// Substring
std::string sub = s3.substr(0, 5);  // "Hello"

// Find
size_t pos = s3.find("World");  // 6
if (pos != std::string::npos) { /* found */ }

Useful String Methods

MethodWhat It DoesExample
.size() / .length()Number of characterss.size()
.empty()True if string is ""s.empty()
.substr(pos, len)Extract substrings.substr(0, 5)
.find(str)Find position of substrings.find("hello")
.push_back(c)Append a characters.push_back('!')
.pop_back()Remove last characters.pop_back()
.append(str)Append a strings.append(" end")
.erase(pos, len)Remove characterss.erase(0, 3)
.c_str()Get C-string versions.c_str()

String Iteration

C++
std::string word = "Hello";

// By index
for (int i = 0; i < word.size(); i++)
    std::cout << word[i];

// Range-based (modern, preferred)
for (char c : word)
    std::cout << c;

// By reference (if modifying)
for (char& c : word)
    c = toupper(c);  // Uppercase in-place

Reading Strings with Spaces

C++
std::string name;
std::cin >> name;              // Reads only ONE word (stops at space)
std::getline(std::cin, name);  // Reads entire line including spaces

11. Vectors (std::vector)

Vectors are dynamic arrays -- they grow and shrink automatically. They're the most commonly used container in C++ and should be your default choice over raw arrays.

Why Vectors Over Arrays?

  • Dynamic size -- no need to know the size at compile time
  • Knows its own size (.size())
  • Bounds checking with .at()
  • Automatic memory management (no manual new/delete)

Creating and Using Vectors

C++
#include <vector>

// Create
std::vector<int> v1;                    // Empty vector
std::vector<int> v2 = {1, 2, 3, 4, 5};  // Initialized
std::vector<int> v3(10, 0);              // 10 elements, all 0

// Add elements
v1.push_back(10);   // [10]
v1.push_back(20);   // [10, 20]
v1.push_back(30);   // [10, 20, 30]

// Remove last element
v1.pop_back();       // [10, 20]

// Access
std::cout << v2[0];      // 1 (no bounds check)
std::cout << v2.at(0);   // 1 (bounds checked -- throws if invalid)
std::cout << v2.front(); // 1 (first element)
std::cout << v2.back();  // 5 (last element)

// Size
std::cout << v2.size();  // 5
std::cout << v2.empty(); // false

Iterating Over Vectors

C++
std::vector<int> nums = {10, 20, 30, 40};

// Method 1: Index-based
for (int i = 0; i < nums.size(); i++)
    std::cout << nums[i] << " ";

// Method 2: Range-based (preferred)
for (int n : nums)
    std::cout << n << " ";

// Method 3: Range-based by reference (can modify)
for (int& n : nums)
    n *= 2;  // Doubles every element in-place

2D Vectors

C++
// Create a 3x4 grid initialized to 0
std::vector<std::vector<int>> grid(3, std::vector<int>(4, 0));

grid[1][2] = 42;  // Row 1, Column 2

// Iterate 2D
for (auto& row : grid) {
    for (int cell : row)
        std::cout << cell << " ";
    std::cout << "\n";
}

Common Vector Operations

OperationCodeTime
Add to endv.push_back(x)O(1) amortized
Remove from endv.pop_back()O(1)
Access by indexv[i] or v.at(i)O(1)
Insert at positionv.insert(v.begin()+i, x)O(n)
Erase at positionv.erase(v.begin()+i)O(n)
Sizev.size()O(1)
Clear allv.clear()O(n)
Sortsort(v.begin(), v.end())O(n log n)
Reversereverse(v.begin(), v.end())O(n)
reserve() for Performance

If you know roughly how many elements you'll add, call v.reserve(n) first. This pre-allocates memory so the vector doesn't have to resize (copy everything) multiple times as it grows.

12. Memory Management

Stack vs Heap

C++ uses two areas of memory:

+---------------------------+ | STACK | Fast, automatic, limited size | Local variables | Freed when function returns | Function parameters | int x = 5; (lives here) +---------------------------+ | | | HEAP | Slower, manual, large | Dynamically allocated | Freed when YOU say so | new / delete | int* p = new int(5); +---------------------------+
FeatureStackHeap
SpeedVery fastSlower
SizeSmall (~1-8 MB)Large (GBs)
LifetimeAutomatic (scope-based)Manual (you control)
Syntaxint x = 5;int* p = new int(5);

new and delete

C++
// Allocate single value on heap
int* p = new int(42);
std::cout << *p;    // 42
delete p;            // Free the memory
p = nullptr;         // Good practice: avoid dangling pointer

// Allocate array on heap
int* arr = new int[5]{1, 2, 3, 4, 5};
std::cout << arr[2];  // 3
delete[] arr;         // Free array (note the []!)
Memory Leak

If you new something but never delete it, that memory is leaked -- it can't be reused until the program exits. In a long-running server, this means your program slowly eats all available RAM and crashes.

void leak() {
    int* p = new int(42);
    // Function returns without delete -- memory leaked!
}
The Modern Rule

In modern C++, you should almost never use raw new/delete. Use smart pointers (next section) or containers like std::vector that manage memory for you. If you never write new, you can never leak.

13. RAII and Smart Pointers

RAII stands for Resource Acquisition Is Initialization. It's the most important idiom in C++ and the reason C++ can be both low-level and safe.

The Core Idea

RAII means: tie the lifetime of a resource to the lifetime of an object.

  • When the object is created (constructor), it acquires the resource (memory, file handle, lock, etc.)
  • When the object is destroyed (destructor), it releases the resource
  • Since C++ guarantees destructors run when objects go out of scope, the resource is always cleaned up
RAII in Action -- File Handling
// BAD: Manual resource management (C-style)
void readFile() {
    FILE* f = fopen("data.txt", "r");
    // ... what if an exception happens here?
    // fclose(f) never runs -- resource leaked!
    fclose(f);
}

// GOOD: RAII (C++ style)
void readFile() {
    std::ifstream file("data.txt");  // Constructor opens file
    // ... use file ...
}  // Destructor automatically closes file, even if exception!

Smart Pointers

Smart pointers are the RAII solution for heap memory. They wrap a raw pointer and automatically call delete when they go out of scope.

unique_ptr (Sole Ownership)

Exactly one owner. When the unique_ptr goes out of scope, it deletes the memory. Can't be copied, only moved.

C++
#include <memory>

// Create
std::unique_ptr<int> p = std::make_unique<int>(42);
std::cout << *p;  // 42

// Transfer ownership (move)
std::unique_ptr<int> p2 = std::move(p);
// p is now nullptr, p2 owns the memory

// Automatically deleted when p2 goes out of scope
// No delete needed! No leaks possible!

shared_ptr (Shared Ownership)

Multiple owners. Uses reference counting. Memory is freed when the LAST shared_ptr is destroyed.

C++
std::shared_ptr<int> p1 = std::make_shared<int>(42);
std::shared_ptr<int> p2 = p1;  // Both point to same memory

std::cout << p1.use_count();  // 2 (two owners)

p1.reset();  // p1 releases ownership
std::cout << p2.use_count();  // 1
// Memory freed when p2 is destroyed (last owner)

weak_ptr (Non-Owning Observer)

Watches a shared_ptr without owning it. Prevents circular references.

C++
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp;  // Doesn't increase ref count

if (auto locked = wp.lock()) {  // Try to get a shared_ptr
    std::cout << *locked;       // 42
}

Which Smart Pointer to Use?

TypeUse WhenOverhead
unique_ptrSingle owner (95% of cases)Zero (same as raw pointer)
shared_ptrMultiple owners neededReference count (small)
weak_ptrObserving without owningMinimal
Modern C++ Rule:
1. Use unique_ptr by default
2. Use shared_ptr when you need shared ownership
3. Never use raw new/delete in application code
4. Use make_unique and make_shared to create smart pointers

14. Structs and Classes

Structs

A struct groups related data together:

C++
struct Point {
    double x;
    double y;
};

Point p{3.0, 4.0};
std::cout << p.x << ", " << p.y;  // 3, 4

Classes

Same as structs but with private by default and the full power of OOP:

C++
class Rectangle {
private:
    double width;
    double height;

public:
    // Constructor
    Rectangle(double w, double h) : width(w), height(h) {}

    // Destructor
    ~Rectangle() { /* cleanup if needed */ }

    // Member functions
    double area() const { return width * height; }
    double perimeter() const { return 2 * (width + height); }
};

Rectangle r(5.0, 3.0);
std::cout << r.area();       // 15
std::cout << r.perimeter(); // 16

The this Pointer

Inside a member function, this is a pointer to the current object:

C++
class Counter {
    int count = 0;
public:
    Counter& increment() {
        this->count++;
        return *this;  // Return reference to self (enables chaining)
    }
};

Counter c;
c.increment().increment().increment();  // Method chaining!

Operator Overloading

C++
struct Vec2 {
    double x, y;

    Vec2 operator+(const Vec2& other) const {
        return {x + other.x, y + other.y};
    }

    bool operator==(const Vec2& other) const {
        return x == other.x && y == other.y;
    }
};

Vec2 a{1, 2}, b{3, 4};
Vec2 c = a + b;  // {4, 6}

15. Modern C++ Features

Lambda Expressions

Anonymous functions you can define inline:

C++
// Basic lambda
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 4);  // 7

// Lambda with capture (access outside variables)
int multiplier = 3;
auto times = [multiplier](int x) { return x * multiplier; };
std::cout << times(5);  // 15

// Lambda for sorting
std::vector<int> v = {3, 1, 4, 1, 5};
std::sort(v.begin(), v.end(), [](int a, int b) {
    return a > b;  // Sort descending
});

std::pair and std::tuple

C++
// Pair: holds exactly 2 values
std::pair<int, std::string> p = {1, "hello"};
std::cout << p.first << " " << p.second;

// Tuple: holds any number of values
auto t = std::make_tuple(1, "hello", 3.14);
std::cout << std::get<0>(t);  // 1

Structured Bindings (C++17)

C++
std::pair<int, std::string> p = {42, "answer"};
auto [num, text] = p;  // Unpack!
std::cout << num << ": " << text;  // 42: answer

// Great for maps
std::map<std::string, int> ages = {{"Alice", 25}, {"Bob", 30}};
for (auto& [name, age] : ages) {
    std::cout << name << ": " << age << "\n";
}

std::optional (C++17)

A value that might or might not exist (like nullable types):

C++
#include <optional>

std::optional<int> findIndex(const std::vector<int>& v, int target) {
    for (int i = 0; i < v.size(); i++)
        if (v[i] == target) return i;
    return std::nullopt;  // Not found
}

auto result = findIndex({1,2,3}, 2);
if (result.has_value())
    std::cout << *result;  // 1

Move Semantics (Brief Intuition)

Instead of copying a large object (expensive), you can move it (fast). Moving transfers ownership of the internal data without copying:

C++
std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = std::move(v1);
// v2 now has the data, v1 is empty
// No elements were copied -- just the internal pointer was transferred

Think of it like giving someone your suitcase vs. buying them a new suitcase and copying everything into it.

16. STL Containers Overview

ContainerDescriptionAccessInsert/DeleteUse When
vectorDynamic arrayO(1)O(1) back, O(n) middleDefault choice
dequeDouble-ended queueO(1)O(1) front/backNeed front insertion
listDoubly linked listO(n)O(1) at iteratorFrequent mid-insertion
setSorted unique elementsO(log n)O(log n)Need sorted uniqueness
mapSorted key-value pairsO(log n)O(log n)Need sorted keys
unordered_setHash setO(1) avgO(1) avgFast lookup
unordered_mapHash mapO(1) avgO(1) avgFast key-value lookup
stackLIFO adapterO(1) topO(1) push/popDFS, bracket matching
queueFIFO adapterO(1) frontO(1) push/popBFS
priority_queueMax-heapO(1) topO(log n)Always want max/min
STL Container Complexity Guarantees:

ContainerAccessInsertDeleteSearchOrdered?
vectorO(1)O(1) amortized (end)O(n)O(n)No
dequeO(1)O(1) (both ends)O(n)O(n)No
listO(n)O(1) (with iterator)O(1) (with iterator)O(n)No
set/mapO(log n)O(log n)O(log n)O(log n)Yes
unordered_set/mapO(1) avgO(1) avgO(1) avgO(1) avgNo
Quick Decision Guide

Need a list of things? Use vector.

Need to check "is X in the collection?" Use unordered_set.

Need key-value mapping? Use unordered_map.

Need sorted order? Use set or map.

Need LIFO/FIFO? Use stack or queue.

17. Common Patterns and Idioms

Common STL Algorithms

C++
#include <algorithm>
#include <numeric>

std::vector<int> v = {5, 2, 8, 1, 9, 3};

// Sort
std::sort(v.begin(), v.end());           // Ascending: 1 2 3 5 8 9
std::sort(v.begin(), v.end(), std::greater<>());  // Descending

// Find
auto it = std::find(v.begin(), v.end(), 5);
if (it != v.end()) std::cout << "Found at index " << (it - v.begin());

// Binary search (requires sorted container)
bool found = std::binary_search(v.begin(), v.end(), 5);

// Sum all elements
int total = std::accumulate(v.begin(), v.end(), 0);

// Min and Max
int mx = *std::max_element(v.begin(), v.end());
int mn = *std::min_element(v.begin(), v.end());

// Reverse
std::reverse(v.begin(), v.end());

// Remove duplicates (must be sorted first)
std::sort(v.begin(), v.end());
v.erase(std::unique(v.begin(), v.end()), v.end());

Competitive Programming Template

C++
#include <bits/stdc++.h>  // Includes everything (GCC only)
using namespace std;

typedef long long ll;
typedef vector<int> vi;
typedef pair<int,int> pii;
#define pb push_back
#define all(x) (x).begin(), (x).end()

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    vi a(n);
    for (int& x : a) cin >> x;

    // Solve...

    return 0;
}
Why ios_base::sync_with_stdio(false)?

By default, C++ synchronizes cin/cout with C's stdio. Disabling this makes I/O much faster -- critical in competitive programming where reading 10^5 numbers with sync on can TLE.

Reading Input Patterns

C++
// Read n numbers into a vector
int n;
cin >> n;
vector<int> v(n);
for (int& x : v) cin >> x;

// Read until EOF
int x;
while (cin >> x) {
    // process x
}

// Read a 2D grid
int rows, cols;
cin >> rows >> cols;
vector<vector<int>> grid(rows, vector<int>(cols));
for (auto& row : grid)
    for (int& cell : row)
        cin >> cell;

18. Practice Quiz

Test your understanding of C++ concepts. Click an answer to check it.

Q1: What does int x; (without initialization) contain?

B) Garbage. In C++, local variables are NOT automatically initialized to 0. They contain whatever was previously in that memory location. Always initialize your variables.

Q2: What is the output of 7 / 2 in C++ when both operands are int?

A) 3. Integer division truncates the decimal part. To get 3.5, you need at least one operand to be a floating-point type: 7.0 / 2 or static_cast<double>(7) / 2.

Q3: Given int x = 5; int* p = &x;, what does *p = 10; do?

B) Changes x to 10. The * operator dereferences the pointer -- it goes to the address p points to (which is x's address) and writes 10 there. Since p points to x, this modifies x.

Q4: What is the key advantage of unique_ptr over raw pointers?

B) Automatic cleanup. unique_ptr implements RAII -- when it goes out of scope, its destructor automatically calls delete. This prevents memory leaks with zero overhead compared to a raw pointer.

Q5: What does void foo(int& x) mean?

B) Pass by reference. The & in the parameter means x is an alias for the original variable. Any changes to x inside the function will affect the caller's variable directly.

Q6: What does RAII stand for and what does it mean?

B) Resource Acquisition Is Initialization. RAII means that when an object is created, it acquires a resource (memory, file, lock), and when the object is destroyed (goes out of scope), it releases the resource. This guarantees cleanup even if exceptions occur.

Q7: Given int** pp;, what is pp?

B) A pointer to a pointer to an int. Each * adds a level of indirection. int** means: this variable holds the address of another pointer, which itself holds the address of an int. Dereferencing twice (**pp) gets you the int value.

Q8: What happens when you push_back to a vector that is at capacity?

C) Reallocates and copies. Vectors typically double their capacity when full. This makes push_back O(1) amortized -- most calls are O(1), but occasionally one is O(n) for the copy. Use reserve() to pre-allocate if you know the size.
← Advanced DSA Home →