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
| Feature | C++ | Python | Java |
|---|---|---|---|
| Speed | Extremely fast | Slow (interpreted) | Fast (JIT compiled) |
| Memory control | Full manual control | Garbage collected | Garbage collected |
| Learning curve | Steep | Gentle | Moderate |
| Use case | Systems, games, perf-critical | Scripting, ML, web | Enterprise, Android |
How C++ Code Runs
Unlike Python (interpreted line by line), C++ is compiled. Your code goes through several steps:
You write .cpp files, the compiler (like g++) turns them into machine code, and the linker combines everything into an executable you can run.
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 (likeimportin Python)int main()-- the entry point. Every C++ program must have exactly onemainfunctionstd::cout-- "character output" -- prints to the console. The<<operator sends data to itstd::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;
}
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
| Type | Size (typical) | Range | Example |
|---|---|---|---|
bool | 1 byte | true / false | bool alive = true; |
char | 1 byte | -128 to 127 (or a character) | char grade = 'A'; |
int | 4 bytes | -2.1 billion to 2.1 billion | int count = 42; |
long long | 8 bytes | -9.2 quintillion to 9.2 quintillion | long long big = 1e18; |
float | 4 bytes | ~7 decimal digits precision | float pi = 3.14f; |
double | 8 bytes | ~15 decimal digits precision | double pi = 3.14159265; |
void | 0 | No 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:
unsigned int: 0 to 4,294,967,295
Integer 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
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
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ | Addition | 5 + 3 | 8 |
- | Subtraction | 5 - 3 | 2 |
* | Multiplication | 5 * 3 | 15 |
/ | Division | 7 / 2 | 3 (integer division!) |
% | Modulus (remainder) | 7 % 2 | 1 |
++ | Increment | x++ | x = x + 1 |
-- | Decrement | x-- | x = x - 1 |
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
| Operator | Meaning | Example |
|---|---|---|
== | Equal to | x == 5 |
!= | Not equal to | x != 5 |
<, > | Less/greater than | x < 10 |
<=, >= | Less/greater or equal | x >= 0 |
&& | Logical AND | x > 0 && x < 10 |
|| | Logical OR | x == 0 || x == 1 |
! | Logical NOT | !done |
Bitwise Operators
| Operator | Meaning | Example |
|---|---|---|
& | AND | 5 & 3 = 1 |
| | OR | 5 | 3 = 7 |
^ | XOR | 5 ^ 3 = 6 |
~ | NOT (flip bits) | ~5 |
<< | Left shift | 1 << 3 = 8 |
>> | Right shift | 8 >> 2 = 2 |
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;
}
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!
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")
*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
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)
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)
- 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
- 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
newsomething but neverdeleteit. 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
| Feature | Reference | Pointer |
|---|---|---|
| Syntax | int& ref = x; | int* ptr = &x; |
| Can be null? | No | Yes (nullptr) |
| Can be reassigned? | No (always refers to same variable) | Yes (can point to different things) |
| Must be initialized? | Yes | No (but should be) |
| Dereference needed? | No (use directly) | Yes (*ptr) |
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)
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
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
| Method | What It Does | Example |
|---|---|---|
.size() / .length() | Number of characters | s.size() |
.empty() | True if string is "" | s.empty() |
.substr(pos, len) | Extract substring | s.substr(0, 5) |
.find(str) | Find position of substring | s.find("hello") |
.push_back(c) | Append a character | s.push_back('!') |
.pop_back() | Remove last character | s.pop_back() |
.append(str) | Append a string | s.append(" end") |
.erase(pos, len) | Remove characters | s.erase(0, 3) |
.c_str() | Get C-string version | s.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
| Operation | Code | Time |
|---|---|---|
| Add to end | v.push_back(x) | O(1) amortized |
| Remove from end | v.pop_back() | O(1) |
| Access by index | v[i] or v.at(i) | O(1) |
| Insert at position | v.insert(v.begin()+i, x) | O(n) |
| Erase at position | v.erase(v.begin()+i) | O(n) |
| Size | v.size() | O(1) |
| Clear all | v.clear() | O(n) |
| Sort | sort(v.begin(), v.end()) | O(n log n) |
| Reverse | reverse(v.begin(), v.end()) | O(n) |
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:
| Feature | Stack | Heap |
|---|---|---|
| Speed | Very fast | Slower |
| Size | Small (~1-8 MB) | Large (GBs) |
| Lifetime | Automatic (scope-based) | Manual (you control) |
| Syntax | int 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 []!)
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!
}
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
// 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?
| Type | Use When | Overhead |
|---|---|---|
unique_ptr | Single owner (95% of cases) | Zero (same as raw pointer) |
shared_ptr | Multiple owners needed | Reference count (small) |
weak_ptr | Observing without owning | Minimal |
1. Use
unique_ptr by default2. Use
shared_ptr when you need shared ownership3. Never use raw
new/delete in application code4. 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
| Container | Description | Access | Insert/Delete | Use When |
|---|---|---|---|---|
vector | Dynamic array | O(1) | O(1) back, O(n) middle | Default choice |
deque | Double-ended queue | O(1) | O(1) front/back | Need front insertion |
list | Doubly linked list | O(n) | O(1) at iterator | Frequent mid-insertion |
set | Sorted unique elements | O(log n) | O(log n) | Need sorted uniqueness |
map | Sorted key-value pairs | O(log n) | O(log n) | Need sorted keys |
unordered_set | Hash set | O(1) avg | O(1) avg | Fast lookup |
unordered_map | Hash map | O(1) avg | O(1) avg | Fast key-value lookup |
stack | LIFO adapter | O(1) top | O(1) push/pop | DFS, bracket matching |
queue | FIFO adapter | O(1) front | O(1) push/pop | BFS |
priority_queue | Max-heap | O(1) top | O(log n) | Always want max/min |
| Container | Access | Insert | Delete | Search | Ordered? |
|---|---|---|---|---|---|
| vector | O(1) | O(1) amortized (end) | O(n) | O(n) | No |
| deque | O(1) | O(1) (both ends) | O(n) | O(n) | No |
| list | O(n) | O(1) (with iterator) | O(1) (with iterator) | O(n) | No |
| set/map | O(log n) | O(log n) | O(log n) | O(log n) | Yes |
| unordered_set/map | O(1) avg | O(1) avg | O(1) avg | O(1) avg | No |
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;
}
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?
Q2: What is the output of 7 / 2 in C++ when both operands are int?
7.0 / 2 or static_cast<double>(7) / 2.
Q3: Given int x = 5; int* p = &x;, what does *p = 10; do?
* 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?
Q5: What does void foo(int& x) mean?
& 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?
Q7: Given int** pp;, what is pp?
* 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?
reserve() to pre-allocate if you know the size.