Home /C++ /Crash Course

C++ Course

C++ Crash Course

The Essential Reference Guide — Modern C++20

1. Hello World & Program Structure

Every C++ program has a main() function — the entry point. Headers are included with #include and the std namespace provides standard library features.

#include <iostream>
#include <string>

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

    // using namespace std; avoids the std:: prefix
    using namespace std;
    string name = "Ayman";
    cout << "Hello, " << name << endl;

    return 0;   // 0 = success
}

C++ vs Java/C#

Unlike Java or C#, C++ compiles directly to machine code — no virtual machine. This gives maximum performance but requires you to manage memory manually (unless you use smart pointers).

2. Variables & Data Types

C++ is statically typed. Use auto for type inference (C++11+).

// Fundamental types
int    age     = 25;
double gpa     = 3.85;
float  price   = 9.99f;
char   grade   = 'A';
bool   active  = true;
long long big  = 9'000'000'000LL;  // digit separator C++14

// auto — compiler infers the type
auto score   = 95;          // int
auto name    = std::string{"Ayman"};

// const — value cannot change
const double PI = 3.14159;

// constexpr — evaluated at compile time
constexpr int MAX_SIZE = 100;

// std::string
std::string msg = "Hello, C++!";
std::cout << msg.length() << std::endl;   // 11

3. Pointers & References

Pointers and references are the most important — and most misunderstood — feature of C++. A pointer stores a memory address. A reference is an alias for an existing variable.

int x = 42;

// Pointer — stores the address of x
int* ptr = &x;            // & = address-of operator
std::cout << ptr  << "\n";  // prints memory address
std::cout << *ptr << "\n";  // * = dereference → 42
*ptr = 99;               // modifies x through pointer

// Null pointer (modern C++)
int* p = nullptr;

// Reference — an alias, cannot be rebound
int& ref = x;
ref = 100;               // same as x = 100

// Pointer arithmetic
int arr[] = {10, 20, 30};
int* p2 = arr;
std::cout << *(p2 + 1);   // 20 — second element

// Dynamic allocation
int* heap = new int(55);   // allocate on heap
delete heap;               // MUST free — memory leak if omitted
heap = nullptr;            // good habit after delete

⚠ Memory Safety

Every new must be matched with a delete. In modern C++ prefer std::unique_ptr or std::shared_ptr (Section 10) — they delete automatically.

4. Control Flow

int score = 85;

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

// switch
switch (score / 10) {
    case 10: case 9: std::cout << "A"; break;
    case 8:            std::cout << "B"; break;
    default:           std::cout << "F"; break;
}

// Ternary
std::string result = (score >= 60) ? "Pass" : "Fail";

// Loops
for (int i = 0; i < 5; i++) std::cout << i << " ";

int count = 0;
while (count < 3) { std::cout << count++; }

// Range-based for (C++11)
std::vector<int> nums = {1, 2, 3, 4};
for (const auto& n : nums) std::cout << n << " ";

5. Functions

// Pass by value — copy is made
int square(int n) { return n * n; }

// Pass by reference — modifies original
void doubleIt(int& n) { n *= 2; }

// Pass by const reference — read-only, no copy (efficient for large types)
void print(const std::string& msg) {
    std::cout << msg << std::endl;
}

// Default parameters
double power(double base, int exp = 2) {
    double result = 1;
    for (int i = 0; i < exp; i++) result *= base;
    return result;
}

// Function overloading — same name, different parameters
int    add(int a,    int b)    { return a + b; }
double add(double a, double b) { return a + b; }

// Inline — suggests compiler to expand at call site
inline int max2(int a, int b) { return (a > b) ? a : b; }

Best Practice

Prefer const T& over T for large parameters — passing by const reference avoids copying without allowing modification.

6. Arrays & STL Containers

#include <vector>
#include <map>
#include <set>
#include <array>

// C-style array (avoid in modern C++)
int raw[3] = {10, 20, 30};

// std::array — fixed-size, safe (C++11)
std::array<int, 3> arr = {10, 20, 30};
std::cout << arr.size();   // 3

// std::vector — dynamic array (most common)
std::vector<int> v = {1, 2, 3};
v.push_back(4);
v.pop_back();
std::cout << v[0];          // 1
std::cout << v.size();       // 3

// std::map — sorted key-value pairs
std::map<std::string, int> grades;
grades["Ayman"] = 95;
grades["Sara"]  = 88;
for (const auto& [name, grade] : grades)  // structured bindings C++17
    std::cout << name << ": " << grade << "\n";

// std::set — unique sorted values
std::set<int> unique = {3, 1, 4, 1, 5};  // stores {1,3,4,5}

7. OOP & Classes

class Student {
private:
    std::string name;
    int grade;

public:
    // Constructor
    Student(const std::string& n, int g)
        : name(n), grade(g) {}   // initializer list (preferred)

    // Destructor — called when object is destroyed
    ~Student() {}

    // Getters (const — do not modify object)
    std::string getName()  const { return name;  }
    int         getGrade() const { return grade; }

    // Method
    std::string letterGrade() const {
        if (grade >= 90) return "A";
        if (grade >= 80) return "B";
        if (grade >= 70) return "C";
        return "F";
    }

    // Operator overloading
    bool operator<(const Student& other) const {
        return grade < other.grade;
    }
};

// Usage
Student s("Ayman", 92);
std::cout << s.getName() << ": " << s.letterGrade();   // Ayman: A

8. Inheritance & Polymorphism

// Base class
class Shape {
public:
    virtual double area() const = 0;   // pure virtual = abstract
    virtual ~Shape() {}                // virtual destructor — essential
    virtual std::string describe() const {
        return "Shape with area: " + std::to_string(area());
    }
};

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

class Rectangle : public Shape {
    double w, h;
public:
    Rectangle(double w, double h) : w(w), h(h) {}
    double area() const override { return w * h; }
};

// Polymorphism via base pointer
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(4.0, 6.0));

for (const auto& s : shapes)
    std::cout << s->area() << "\n";

override keyword

Always use override when overriding virtual functions — the compiler will error if the signature doesn't match the base, catching subtle bugs.

9. Templates

Templates allow writing type-generic code — the foundation of the STL. The compiler generates a concrete version for each type used.

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

std::cout << maxVal(3, 7);          // 7  (int)
std::cout << maxVal(3.14, 2.71);    // 3.14 (double)
std::cout << maxVal(std::string{"a"}, std::string{"z"}); // z

// Class template
template<typename T>
class Box {
    T value;
public:
    Box(T v) : value(v) {}
    T get() const { return value; }
};

Box<int>         iBox(42);
Box<std::string> sBox("Hello");
std::cout << iBox.get();   // 42

// Template with constraint (C++20 concepts)
template<std::integral T>   // only integer types
T factorial(T n) {
    return (n <= 1) ? 1 : n * factorial(n - 1);
}

10. Smart Pointers

Smart pointers manage memory automatically — no manual delete needed. They follow RAII: resource is freed when the pointer goes out of scope.

#include <memory>

// unique_ptr — sole owner, cannot be copied
std::unique_ptr<int> uptr = std::make_unique<int>(42);
std::cout << *uptr;         // 42
// Automatically deleted when uptr goes out of scope

// shared_ptr — reference counted, can be shared
std::shared_ptr<int> sp1 = std::make_shared<int>(99);
std::shared_ptr<int> sp2 = sp1;   // both point to same int
std::cout << sp1.use_count();     // 2 — two owners
// Deleted when last shared_ptr is destroyed

// weak_ptr — observes without owning (breaks circular refs)
std::weak_ptr<int> wp = sp1;
if (auto locked = wp.lock()) {
    std::cout << *locked;   // safe access
}

// Smart pointer with custom class
auto student = std::make_unique<Student>("Ayman", 92);
std::cout << student->getName();   // use -> to access members

Rule of Thumb

Default to unique_ptr. Use shared_ptr only when you genuinely need shared ownership. Never use raw new/delete in modern C++ unless working with legacy code.

11. Lambda & STL Algorithms

#include <algorithm>
#include <numeric>

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

// Lambda syntax: [capture](params) { body }
auto isEven = [](int n) { return n % 2 == 0; };

// Sort ascending
std::sort(v.begin(), v.end());

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

// Count even numbers
auto cnt = std::count_if(v.begin(), v.end(), isEven);

// Find first element > 5
auto it = std::find_if(v.begin(), v.end(),
                       [](int n){ return n > 5; });

// Transform — square each element into new vector
std::vector<int> squares;
std::transform(v.begin(), v.end(),
               std::back_inserter(squares),
               [](int n){ return n * n; });

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

// Capture by reference in lambda
int threshold = 4;
auto aboveThreshold = [&threshold](int n){ return n > threshold; };

12. Modern C++20 Features

Concepts — type constraints

#include <concepts>

template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;

template<Numeric T>
T add(T a, T b) { return a + b; }   // only compiles for numeric types

Ranges — cleaner algorithms

#include <ranges>

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

// Ranges: filter + transform pipeline (no iterators needed)
auto result = v
    | std::views::filter([](int n){ return n % 2 == 0; })
    | std::views::transform([](int n){ return n * n; });
// result = {4, 16, 36} — lazy evaluated

Coroutines & std::span

#include <span>

// std::span — non-owning view over contiguous data (C++20)
void printAll(std::span<const int> data) {
    for (int x : data) std::cout << x << " ";
}

int arr[] = {1, 2, 3};
std::vector<int> vec = {4, 5, 6};
printAll(arr);   // works with both array and vector
printAll(vec);