Toolmingo
Guides23 min read

C++ Tutorial for Beginners (2025): Learn C++ Step by Step

Complete C++ tutorial for beginners. Learn C++ syntax, OOP, memory management, STL, and build real projects. Free guide with code examples.

C++ is one of the most powerful programming languages ever created. It combines the low-level control of C with high-level abstractions, object-oriented programming, and the Standard Template Library (STL). C++ powers game engines (Unreal Engine), operating systems, browsers (Chrome's V8 engine core), databases (MySQL), and embedded systems. This tutorial takes you from zero to writing real C++ programs, step by step, with no prior C++ experience required.

What you'll learn

Topic What you'll be able to do
Setup Install a C++ compiler and write your first program
Syntax & variables Understand types, operators, and expressions
Control flow Use if/else, switch, loops, and functions
OOP Build classes, objects, inheritance, and polymorphism
Memory Work with pointers, references, and dynamic memory
STL Use vectors, maps, sets, and algorithms
Modern C++ Apply C++11/14/17/20 features

Why learn C++?

Use Case Examples
Game development Unreal Engine, AAA game studios
Systems programming Operating systems, drivers, compilers
High-performance software Trading systems, scientific computing
Embedded / IoT Microcontrollers, robotics, automotive
Graphics & engines OpenGL, Vulkan, DirectX applications
Browsers & databases Chrome, Firefox, MySQL, SQLite
Competitive programming Most performance-critical contest solutions

C++ vs other languages

Feature C++ C Java Python Rust
Performance Excellent Excellent Good Moderate Excellent
Memory control Manual Manual GC GC Ownership
OOP support Full None Full Full Partial
Learning curve Steep Moderate Moderate Easy Very steep
Ecosystem Huge Large Huge Huge Growing
Use case Systems/games/perf Systems/embedded Enterprise Scripting/ML Systems/safety

Setup: Install a C++ compiler

Windows (MinGW-w64 via MSYS2)

# 1. Download and install MSYS2 from https://www.msys2.org/
# 2. In MSYS2 terminal:
pacman -S mingw-w64-ucrt-x86_64-gcc

# 3. Add C:\msys64\ucrt64\bin to PATH
# 4. Verify:
g++ --version

Windows (Visual Studio)

Download Visual Studio Community (free) — includes MSVC compiler. Select "Desktop development with C++".

macOS

# Install Xcode command line tools (includes Clang):
xcode-select --install

# Or install via Homebrew:
brew install gcc

clang++ --version

Linux (Ubuntu/Debian)

sudo apt update
sudo apt install build-essential g++
g++ --version

Recommended editor: VS Code

Install VS Code + the C/C++ extension (Microsoft). For a full IDE: CLion (JetBrains) or Visual Studio (Windows).


Hello, World!

#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

Compile and run:

g++ hello.cpp -o hello
./hello          # Linux/macOS
hello.exe        # Windows

What each line means

  • #include <iostream> — include the input/output library
  • int main() — entry point of every C++ program
  • std::cout << "Hello, World!" << std::endl; — print to console
  • return 0; — signal success to the OS

Variables and data types

Basic types

#include <iostream>
#include <string>

int main() {
    // Integer types
    int age = 25;
    long population = 8000000000L;
    short small = 100;

    // Floating point
    double pi = 3.14159265;
    float approx = 3.14f;

    // Boolean
    bool isActive = true;

    // Character
    char grade = 'A';

    // String (from <string>)
    std::string name = "Alice";

    std::cout << name << " is " << age << " years old\n";
    return 0;
}

Type sizes (platform-dependent, 64-bit typical)

Type Size Range
bool 1 byte true / false
char 1 byte -128 to 127
int 4 bytes -2.1B to 2.1B
long long 8 bytes ±9.2 quintillion
float 4 bytes ~7 significant digits
double 8 bytes ~15 significant digits

Use int for most integers, double for most decimals, std::string for text.

auto keyword (C++11)

auto x = 42;         // int
auto y = 3.14;       // double
auto s = std::string("hello"); // std::string
auto flag = true;    // bool

Constants

const double PI = 3.14159265358979;
constexpr int MAX_SIZE = 1000;  // compile-time constant (prefer this)

Strings

#include <iostream>
#include <string>

int main() {
    std::string s = "Hello, C++!";

    // Length
    std::cout << s.length() << "\n";        // 11

    // Concatenation
    std::string full = "Hello" + std::string(", World!");

    // Substring
    std::cout << s.substr(7, 3) << "\n";    // C++

    // Find
    size_t pos = s.find("C++");
    if (pos != std::string::npos) {
        std::cout << "Found at " << pos << "\n"; // 7
    }

    // Replace
    s.replace(7, 3, "World");
    std::cout << s << "\n";                 // Hello, World!

    // Access individual characters
    std::cout << s[0] << "\n";              // H

    return 0;
}

String methods quick reference

Method Purpose
s.length() / s.size() Number of characters
s.empty() True if string is empty
s.substr(pos, len) Extract substring
s.find(sub) Find first occurrence (npos if not found)
s.replace(pos, len, str) Replace portion
s.append(str) / s += str Append
s.erase(pos, len) Remove portion
s.at(i) Access with bounds checking
s.c_str() Convert to C-style const char*

Control flow

if / else

int score = 85;

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

Ternary operator

int x = 10;
std::string result = (x > 0) ? "positive" : "non-positive";

switch

char grade = 'B';

switch (grade) {
    case 'A':
        std::cout << "Excellent\n";
        break;
    case 'B':
        std::cout << "Good\n";
        break;
    case 'C':
        std::cout << "Average\n";
        break;
    default:
        std::cout << "Unknown\n";
}

Loops

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

// while loop
int n = 10;
while (n > 0) {
    std::cout << n << " ";
    n -= 3;
}
// 10 7 4 1

// do-while (runs at least once)
int count = 0;
do {
    count++;
} while (count < 5);

// Range-based for (C++11) — works on arrays, vectors, strings
std::vector<int> nums = {1, 2, 3, 4, 5};
for (int n : nums) {
    std::cout << n << " ";
}

// break and continue
for (int i = 0; i < 10; i++) {
    if (i == 5) break;
    if (i % 2 == 0) continue;
    std::cout << i << " ";  // 1 3
}

Functions

#include <iostream>

// Function declaration (prototype)
int add(int a, int b);
double power(double base, int exp = 2);   // default parameter

// Function definitions
int add(int a, int b) {
    return a + b;
}

double power(double base, int exp) {
    double result = 1.0;
    for (int i = 0; i < exp; i++) {
        result *= base;
    }
    return result;
}

// Multiple return values via reference parameters
void minMax(int arr[], int size, int& minVal, int& maxVal) {
    minVal = maxVal = arr[0];
    for (int i = 1; i < size; i++) {
        if (arr[i] < minVal) minVal = arr[i];
        if (arr[i] > maxVal) maxVal = arr[i];
    }
}

// Function overloading
double add(double a, double b) { return a + b; }

int main() {
    std::cout << add(3, 4) << "\n";           // 7
    std::cout << add(1.5, 2.5) << "\n";       // 4.0
    std::cout << power(2.0) << "\n";          // 4.0
    std::cout << power(2.0, 3) << "\n";       // 8.0

    int arr[] = {5, 2, 8, 1, 9, 3};
    int lo, hi;
    minMax(arr, 6, lo, hi);
    std::cout << lo << " " << hi << "\n";     // 1 9

    return 0;
}

Pass by value vs reference vs pointer

void byValue(int x)   { x = 100; }           // caller unchanged
void byRef(int& x)    { x = 100; }           // modifies caller's variable
void byPtr(int* x)    { *x = 100; }          // modifies via pointer

int n = 5;
byValue(n);  // n still 5
byRef(n);    // n is now 100
byPtr(&n);   // n is now 100

Arrays and vectors

C-style arrays (fixed size)

int scores[5] = {90, 85, 78, 92, 88};
int size = sizeof(scores) / sizeof(scores[0]);  // 5

for (int i = 0; i < size; i++) {
    std::cout << scores[i] << " ";
}

std::vector (dynamic array — prefer this)

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> v = {3, 1, 4, 1, 5, 9};

    // Add / remove
    v.push_back(2);           // {3,1,4,1,5,9,2}
    v.pop_back();             // {3,1,4,1,5,9}
    v.insert(v.begin(), 0);   // {0,3,1,4,1,5,9}
    v.erase(v.begin());       // {3,1,4,1,5,9}

    // Size info
    std::cout << v.size() << "\n";   // 6
    std::cout << v.empty() << "\n";  // 0 (false)

    // Sort
    std::sort(v.begin(), v.end());   // {1,1,3,4,5,9}

    // Range-based for
    for (int x : v) {
        std::cout << x << " ";
    }

    // Find
    auto it = std::find(v.begin(), v.end(), 4);
    if (it != v.end()) {
        std::cout << "\nFound 4 at index " << (it - v.begin()) << "\n";
    }

    return 0;
}

2D vector

std::vector<std::vector<int>> matrix(3, std::vector<int>(3, 0));
matrix[1][1] = 5;

Pointers and references

Pointers are one of C++'s most distinctive (and initially confusing) features. They store memory addresses.

#include <iostream>

int main() {
    int x = 42;

    // Reference: alias for x
    int& ref = x;
    ref = 100;        // x is now 100

    // Pointer: stores address of x
    int* ptr = &x;    // & gets address of x
    std::cout << ptr << "\n";   // memory address e.g. 0x7fff...
    std::cout << *ptr << "\n";  // dereference: 100

    *ptr = 200;       // x is now 200

    // Null pointer
    int* np = nullptr;  // don't use NULL in modern C++
    if (np == nullptr) {
        std::cout << "null pointer\n";
    }

    return 0;
}

Dynamic memory

// Allocate on heap
int* p = new int(42);
std::cout << *p << "\n";   // 42
delete p;                   // must free!
p = nullptr;                // good practice

// Array
int* arr = new int[10];
arr[0] = 1;
delete[] arr;               // use delete[] for arrays!

Modern C++ alternative: use smart pointers instead of raw new/delete.


Object-oriented programming (OOP)

Classes and objects

#include <iostream>
#include <string>

class Animal {
private:
    std::string name;
    int age;

public:
    // Constructor
    Animal(const std::string& name, int age)
        : name(name), age(age) {}

    // Destructor
    ~Animal() {
        // cleanup if needed
    }

    // Getters
    std::string getName() const { return name; }
    int getAge() const { return age; }

    // Method
    virtual void speak() const {
        std::cout << name << " makes a sound\n";
    }

    // toString equivalent
    friend std::ostream& operator<<(std::ostream& os, const Animal& a) {
        os << "Animal(" << a.name << ", age=" << a.age << ")";
        return os;
    }
};

int main() {
    Animal cat("Whiskers", 3);
    std::cout << cat.getName() << "\n";  // Whiskers
    cat.speak();                          // Whiskers makes a sound
    std::cout << cat << "\n";            // Animal(Whiskers, age=3)
    return 0;
}

Inheritance

class Dog : public Animal {
private:
    std::string breed;

public:
    Dog(const std::string& name, int age, const std::string& breed)
        : Animal(name, age), breed(breed) {}

    // Override virtual method
    void speak() const override {
        std::cout << getName() << " says: Woof!\n";
    }

    std::string getBreed() const { return breed; }
};

class Cat : public Animal {
public:
    Cat(const std::string& name, int age) : Animal(name, age) {}

    void speak() const override {
        std::cout << getName() << " says: Meow!\n";
    }
};

Polymorphism

#include <vector>
#include <memory>

int main() {
    // Polymorphism via pointers/references
    std::vector<std::unique_ptr<Animal>> animals;
    animals.push_back(std::make_unique<Dog>("Rex", 5, "Labrador"));
    animals.push_back(std::make_unique<Cat>("Luna", 2));

    for (const auto& a : animals) {
        a->speak();   // calls correct speak() based on actual type
    }
    // Rex says: Woof!
    // Luna says: Meow!

    return 0;
}

Access modifiers

Modifier Same class Derived class Outside
public Yes Yes Yes
protected Yes Yes No
private Yes No No

Interfaces via abstract classes

class Shape {
public:
    virtual double area() const = 0;      // pure virtual
    virtual double perimeter() const = 0;
    virtual ~Shape() = default;            // always virtual destructor
};

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    double area() const override { return 3.14159 * radius * radius; }
    double perimeter() const override { return 2 * 3.14159 * radius; }
};

class Rectangle : public Shape {
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    double area() const override { return width * height; }
    double perimeter() const override { return 2 * (width + height); }
};

The four pillars of OOP

Pillar What it means C++ mechanism
Encapsulation Hide internal state private/protected members
Abstraction Expose only necessary interface Abstract classes, interfaces
Inheritance Reuse and extend existing classes : public BaseClass
Polymorphism Same call, different behavior virtual / override

Smart pointers (Modern C++)

Raw new/delete is error-prone. Use smart pointers from <memory>.

#include <memory>
#include <iostream>

class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource released\n"; }
    void use() { std::cout << "Using resource\n"; }
};

int main() {
    // unique_ptr: sole ownership, auto-delete when out of scope
    {
        auto res = std::make_unique<Resource>();
        res->use();
    } // ~Resource() called here automatically

    // shared_ptr: shared ownership, delete when last owner leaves scope
    auto sp1 = std::make_shared<Resource>();
    {
        auto sp2 = sp1;  // ref count = 2
        sp2->use();
    } // ref count = 1, resource not deleted yet
    // ref count = 0 here, resource deleted

    return 0;
}
Smart pointer Ownership Use when
unique_ptr Exclusive Most cases — sole owner
shared_ptr Shared Multiple owners needed
weak_ptr Non-owning Break circular references

Rule: prefer unique_ptr. Use shared_ptr only when you need shared ownership. Avoid raw new/delete.


The Standard Template Library (STL)

Containers

#include <vector>
#include <list>
#include <map>
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <stack>
#include <queue>

std::map (sorted key-value)

#include <map>
#include <iostream>

int main() {
    std::map<std::string, int> wordCount;

    wordCount["hello"]++;
    wordCount["world"]++;
    wordCount["hello"]++;

    // Iterate (sorted by key)
    for (const auto& [word, count] : wordCount) {
        std::cout << word << ": " << count << "\n";
    }
    // hello: 2
    // world: 1

    // Check existence
    if (wordCount.count("hello")) {
        std::cout << "Found hello\n";
    }

    wordCount.erase("world");
    std::cout << wordCount.size() << "\n";  // 1

    return 0;
}

std::unordered_map (hash map — O(1) average)

#include <unordered_map>

std::unordered_map<std::string, int> freq;
freq["apple"] = 5;
freq["banana"] = 3;

std::cout << freq["apple"] << "\n";  // 5

Use unordered_map when order doesn't matter — it's faster than map.

std::set and std::unordered_set

#include <set>

std::set<int> s = {3, 1, 4, 1, 5, 9};  // duplicates removed, sorted
// s = {1, 3, 4, 5, 9}

s.insert(2);
s.erase(3);

if (s.count(4)) {
    std::cout << "4 is in the set\n";
}

std::stack and std::queue

#include <stack>
#include <queue>

std::stack<int> stk;
stk.push(1); stk.push(2); stk.push(3);
while (!stk.empty()) {
    std::cout << stk.top() << " ";  // 3 2 1
    stk.pop();
}

std::queue<int> q;
q.push(1); q.push(2); q.push(3);
while (!q.empty()) {
    std::cout << q.front() << " ";  // 1 2 3
    q.pop();
}

STL algorithms

#include <algorithm>
#include <vector>
#include <numeric>

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

// Sort
std::sort(v.begin(), v.end());                    // ascending
std::sort(v.begin(), v.end(), std::greater<>());  // descending

// Find
auto it = std::find(v.begin(), v.end(), 8);
bool found = (it != v.end());

// Count
int cnt = std::count(v.begin(), v.end(), 3);

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

// Min/max
auto [mn, mx] = std::minmax_element(v.begin(), v.end());
std::cout << *mn << " " << *mx << "\n";

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

// Filter (copy_if)
std::vector<int> evens;
std::copy_if(v.begin(), v.end(), std::back_inserter(evens),
             [](int x) { return x % 2 == 0; });

Container comparison

Container Access Insert/Delete Ordered Use case
vector O(1) O(n) / O(1) end No General array
list O(n) O(1) anywhere No Frequent middle insert
deque O(1) O(1) both ends No Double-ended queue
map O(log n) O(log n) Yes Sorted key-value
unordered_map O(1) avg O(1) avg No Fast lookup
set O(log n) O(log n) Yes Unique sorted values
stack Top only O(1) No LIFO
queue Front only O(1) No FIFO

Modern C++ features (C++11 to C++20)

Lambda expressions (C++11)

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6};

    // Inline function
    auto square = [](int x) { return x * x; };
    std::cout << square(4) << "\n";  // 16

    // Sort descending with lambda
    std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });

    // Capture outer variable
    int threshold = 3;
    std::vector<int> big;
    std::copy_if(v.begin(), v.end(), std::back_inserter(big),
                 [threshold](int x) { return x > threshold; });
    // big = {4, 5, 6}

    return 0;
}

Range-based for with structured bindings (C++17)

std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}};

for (const auto& [name, score] : scores) {
    std::cout << name << ": " << score << "\n";
}

Initializer lists and uniform initialization (C++11)

std::vector<int> v{1, 2, 3, 4, 5};    // brace initialization
int arr[]{10, 20, 30};

nullptr (C++11)

Always use nullptr instead of NULL or 0 for null pointers.

Move semantics (C++11)

std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> dest = std::move(source);  // no copy — transfers ownership
// source is now empty, dest has the data

constexpr functions (C++11)

constexpr int factorial(int n) {
    return (n <= 1) ? 1 : n * factorial(n - 1);
}
constexpr int f5 = factorial(5);  // computed at compile time: 120

std::optional (C++17)

#include <optional>

std::optional<int> divide(int a, int b) {
    if (b == 0) return std::nullopt;
    return a / b;
}

auto result = divide(10, 2);
if (result) {
    std::cout << *result << "\n";  // 5
}

Ranges (C++20)

#include <ranges>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};

    // Filter + transform with pipe syntax
    for (int x : v | std::views::filter([](int n){ return n % 2 == 0; })
                   | std::views::transform([](int n){ return n * n; })) {
        std::cout << x << " ";  // 4 16 36 64
    }
    return 0;
}

Error handling

Exceptions

#include <iostream>
#include <stdexcept>

double safeDivide(double a, double b) {
    if (b == 0.0) {
        throw std::invalid_argument("Division by zero");
    }
    return a / b;
}

int main() {
    try {
        std::cout << safeDivide(10.0, 2.0) << "\n";  // 5
        std::cout << safeDivide(10.0, 0.0) << "\n";  // throws
    } catch (const std::invalid_argument& e) {
        std::cerr << "Error: " << e.what() << "\n";
    } catch (const std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }
    return 0;
}

Custom exceptions

class ValidationError : public std::exception {
    std::string message;
public:
    explicit ValidationError(const std::string& msg) : message(msg) {}
    const char* what() const noexcept override {
        return message.c_str();
    }
};

Common standard exceptions

Exception When to use
std::invalid_argument Invalid argument to function
std::out_of_range Index out of bounds
std::runtime_error General runtime error
std::overflow_error Arithmetic overflow
std::logic_error Logic bug (precondition violated)
std::bad_alloc Memory allocation failure

File I/O

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

int main() {
    // Write to file
    {
        std::ofstream out("data.txt");
        if (!out) {
            std::cerr << "Cannot open file\n";
            return 1;
        }
        out << "Line 1\n";
        out << "Line 2\n";
    } // file auto-closed here (RAII)

    // Read entire file
    {
        std::ifstream in("data.txt");
        std::string line;
        while (std::getline(in, line)) {
            std::cout << line << "\n";
        }
    }

    // Read into string all at once
    {
        std::ifstream in("data.txt");
        std::ostringstream buffer;
        buffer << in.rdbuf();
        std::string content = buffer.str();
        std::cout << content;
    }

    // Append
    {
        std::ofstream out("data.txt", std::ios::app);
        out << "Line 3\n";
    }

    return 0;
}

Templates (generics)

Templates let you write type-independent code.

#include <iostream>
#include <vector>
#include <algorithm>

// Function template
template <typename T>
T maxOf(T a, T b) {
    return (a > b) ? a : b;
}

// Class template
template <typename T>
class Stack {
    std::vector<T> data;
public:
    void push(const T& val) { data.push_back(val); }
    void pop() { data.pop_back(); }
    T& top() { return data.back(); }
    bool empty() const { return data.empty(); }
    size_t size() const { return data.size(); }
};

int main() {
    std::cout << maxOf(3, 7) << "\n";          // 7
    std::cout << maxOf(3.14, 2.71) << "\n";    // 3.14
    std::cout << maxOf('a', 'z') << "\n";      // z

    Stack<int> s;
    s.push(1); s.push(2); s.push(3);
    std::cout << s.top() << "\n";  // 3
    s.pop();
    std::cout << s.top() << "\n";  // 2

    return 0;
}

3 Real projects

Project 1: Command-line To-Do list

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

struct Task {
    int id;
    std::string title;
    bool done;
};

class TodoList {
    std::vector<Task> tasks;
    int nextId = 1;

public:
    void add(const std::string& title) {
        tasks.push_back({nextId++, title, false});
        std::cout << "Added: " << title << "\n";
    }

    void complete(int id) {
        for (auto& t : tasks) {
            if (t.id == id) {
                t.done = true;
                std::cout << "Completed: " << t.title << "\n";
                return;
            }
        }
        std::cout << "Task " << id << " not found\n";
    }

    void remove(int id) {
        auto it = std::remove_if(tasks.begin(), tasks.end(),
                                  [id](const Task& t){ return t.id == id; });
        if (it != tasks.end()) {
            tasks.erase(it, tasks.end());
            std::cout << "Removed task " << id << "\n";
        }
    }

    void list() const {
        if (tasks.empty()) {
            std::cout << "No tasks.\n";
            return;
        }
        for (const auto& t : tasks) {
            std::cout << "[" << (t.done ? "X" : " ") << "] "
                      << t.id << ". " << t.title << "\n";
        }
    }
};

int main() {
    TodoList todo;
    std::string cmd;

    std::cout << "Commands: add <title> | done <id> | rm <id> | list | quit\n";

    while (std::getline(std::cin, cmd)) {
        if (cmd == "quit") break;
        else if (cmd == "list") todo.list();
        else if (cmd.substr(0, 3) == "add") todo.add(cmd.substr(4));
        else if (cmd.substr(0, 4) == "done") todo.complete(std::stoi(cmd.substr(5)));
        else if (cmd.substr(0, 2) == "rm") todo.remove(std::stoi(cmd.substr(3)));
        else std::cout << "Unknown command\n";
    }
    return 0;
}

Project 2: Word frequency counter

#include <iostream>
#include <fstream>
#include <sstream>
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <cctype>

std::string normalize(std::string word) {
    // lowercase and remove punctuation
    std::string result;
    for (char c : word) {
        if (std::isalpha(c)) result += std::tolower(c);
    }
    return result;
}

int main(int argc, char* argv[]) {
    std::string text;

    if (argc > 1) {
        std::ifstream in(argv[1]);
        std::ostringstream buf;
        buf << in.rdbuf();
        text = buf.str();
    } else {
        std::cout << "Enter text (Ctrl+D to finish):\n";
        std::ostringstream buf;
        buf << std::cin.rdbuf();
        text = buf.str();
    }

    std::unordered_map<std::string, int> freq;
    std::istringstream iss(text);
    std::string token;
    while (iss >> token) {
        std::string word = normalize(token);
        if (!word.empty()) freq[word]++;
    }

    // Sort by frequency descending
    std::vector<std::pair<std::string, int>> sorted(freq.begin(), freq.end());
    std::sort(sorted.begin(), sorted.end(),
              [](const auto& a, const auto& b){ return a.second > b.second; });

    std::cout << "\nTop 10 words:\n";
    int limit = std::min((int)sorted.size(), 10);
    for (int i = 0; i < limit; i++) {
        std::cout << sorted[i].first << ": " << sorted[i].second << "\n";
    }

    return 0;
}
# Compile and run:
g++ -std=c++17 wordfreq.cpp -o wordfreq
echo "the quick brown fox the fox" | ./wordfreq

Project 3: Student grade calculator

#include <iostream>
#include <vector>
#include <string>
#include <numeric>
#include <algorithm>
#include <iomanip>

struct Student {
    std::string name;
    std::vector<double> grades;

    double average() const {
        if (grades.empty()) return 0.0;
        return std::accumulate(grades.begin(), grades.end(), 0.0) / grades.size();
    }

    char letterGrade() const {
        double avg = average();
        if (avg >= 90) return 'A';
        if (avg >= 80) return 'B';
        if (avg >= 70) return 'C';
        if (avg >= 60) return 'D';
        return 'F';
    }
};

void printReport(const std::vector<Student>& students) {
    std::cout << "\n" << std::setw(15) << std::left << "Name"
              << std::setw(10) << "Average"
              << "Grade\n";
    std::cout << std::string(30, '-') << "\n";

    for (const auto& s : students) {
        std::cout << std::setw(15) << std::left << s.name
                  << std::setw(10) << std::fixed << std::setprecision(1) << s.average()
                  << s.letterGrade() << "\n";
    }

    // Class statistics
    double classAvg = 0;
    for (const auto& s : students) classAvg += s.average();
    classAvg /= students.size();

    auto best = std::max_element(students.begin(), students.end(),
        [](const Student& a, const Student& b){ return a.average() < b.average(); });

    std::cout << "\nClass average: " << std::fixed << std::setprecision(1) << classAvg << "\n";
    std::cout << "Top student: " << best->name << " (" << best->average() << ")\n";
}

int main() {
    std::vector<Student> students = {
        {"Alice", {92, 88, 95, 90}},
        {"Bob",   {75, 82, 68, 79}},
        {"Carol", {98, 95, 97, 99}},
        {"Dave",  {60, 55, 70, 65}},
    };

    printReport(students);
    return 0;
}

Learning path

Stage Duration Focus
1. Syntax basics 2–3 weeks Variables, types, control flow, functions
2. OOP 2–3 weeks Classes, inheritance, polymorphism
3. Memory & pointers 2 weeks Pointers, references, smart pointers
4. STL 2 weeks Containers, iterators, algorithms
5. Modern C++ (11/14/17) 2 weeks Lambdas, move semantics, templates
6. Real projects Ongoing Build something: game, tool, library
7. Advanced topics Ongoing Concurrency, networking, performance, C++20

Free resources

Resource Best for
learncpp.com Most complete free C++ tutorial
cppreference.com Authoritative STL/language reference
Stroustrup's A Tour of C++ Fast overview by the language creator
Effective Modern C++ (Meyers) Essential modern C++ best practices
Compiler Explorer (godbolt.org) See what your C++ compiles to
C++ Core Guidelines Official best practices from Stroustrup+Sutter

Common mistakes

Mistake Problem Fix
Using NULL instead of nullptr Type safety issues Always use nullptr
Forgetting delete / delete[] Memory leak Use smart pointers (unique_ptr)
Mixing delete and delete[] Undefined behavior Match new[] with delete[]
Returning reference to local variable Dangling reference Return by value or use heap
Comparing floating point with == Precision error Use std::abs(a - b) < epsilon
Not marking methods const Limits usability Mark getters and read-only methods const
Raw loops over STL algorithms More error-prone Prefer std::sort, std::find, std::count
Using using namespace std; in headers Name collisions Use std:: prefix or limit to .cpp scope

C++ vs related terms

Term What it is
C++ Superset of C with OOP, templates, STL
C Low-level predecessor; no classes or templates
g++ GNU C++ compiler (Linux/macOS/Windows via MinGW)
clang++ LLVM-based C++ compiler; excellent error messages
MSVC Microsoft Visual C++ compiler (Windows)
CMake Build system generator for C++ projects
STL Standard Template Library — containers, algorithms, iterators
Boost Popular C++ library collection, many features adopted into STL
LLVM Compiler infrastructure; powers clang
Qt Cross-platform C++ GUI framework

Frequently asked questions

Is C++ still worth learning in 2025?
Absolutely. C++ is essential for game development (Unreal Engine), systems programming, embedded systems, high-frequency trading, browsers, and competitive programming. It consistently ranks in the top 5 languages by job market demand and offers unmatched performance.

Should I learn C before C++?
Not necessary. C++ is a separate language that includes C as a subset. You can learn C++ directly. Understanding C becomes useful later when working on embedded systems or reading legacy code.

What is the difference between C++11, C++14, C++17, and C++20?
These are ISO standard versions. C++11 was the major modernization (lambdas, smart pointers, move semantics, auto, range-based for). C++14 and C++17 added refinements. C++20 added concepts, ranges, coroutines, and modules. Compile with -std=c++17 or -std=c++20 to enable these features.

What's the difference between struct and class in C++?
Only the default access modifier: struct members are public by default, class members are private by default. Convention: use struct for passive data (no invariants), class for objects with behavior.

How do I compile a C++ program?
g++ -std=c++17 myfile.cpp -o myprogram for most cases. Use -Wall -Wextra to enable warnings. For production, add -O2 for optimization.

When should I use C++ vs Python vs Rust?
Use C++ when you need maximum performance, game development (Unreal), or integrating with existing C++ codebases. Use Python for scripting, data science, and fast prototyping. Use Rust when you need C++ performance with memory safety guarantees and are starting a new project.

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools