C++ Programming Tutorial

Learn modern C++ step-by-step — from basics to advanced features like templates, STL, and smart pointers. Clear explanations with practical, runnable examples.

C++ Pointers and References

Pointers store memory addresses and allow indirect access to data. References are aliases for existing variables. Understanding both is essential for C++ — they enable dynamic memory, data structures, polymorphism, and efficient parameter passing.

References — Safe Aliases

references.cpp
#include <iostream>

int main() {
    int x = 42;
    int& ref = x;  // ref is an alias for x

    std::cout << ref << "
";  // 42
    ref = 100;                  // modifies x through the reference
    std::cout << x << "
";    // 100

    // References MUST be initialized and cannot be reseated
    // int& bad;        // ERROR: uninitialized reference
    // int& ref2 = ref; // ref2 now aliases x, not ref itself

    // Common use: function parameters
    return 0;
}

// Pass by reference — modifies original
void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// Pass by const reference — read-only, no copy
void print(const std::string& msg) {
    std::cout << msg << "
";
    // msg = "x";  // ERROR: const reference
}

Pointers — Memory Addresses

pointers.cpp
#include <iostream>

int main() {
    int x = 42;
    int* ptr = &x;  // ptr stores the address of x

    // Dereference: access value at the address
    std::cout << "Address: " << ptr << "
";   // 0x7ffd...
    std::cout << "Value: " << *ptr << "
";     // 42

    *ptr = 100;  // modify x through pointer
    std::cout << x << "
";  // 100

    // Null pointer — points to nothing
    int* null_ptr = nullptr;
    if (null_ptr == nullptr) {
        std::cout << "Pointer is null
";
    }

    // Pointer arithmetic (arrays)
    int arr[] = {10, 20, 30, 40, 50};
    int* p = arr;  // points to first element
    std::cout << *p << "
";       // 10
    std::cout << *(p + 2) << "
"; // 30
    std::cout << p[3] << "
";     // 40 (same as *(p+3))

    return 0;
}

Pointers vs References

FeaturePointerReference
Syntaxint* p = &x;int& r = x;
Can be nullYes (nullptr)No (must bind to object)
Can be reassignedYes (p = &y;)No (bound at creation)
Needs dereferenceYes (*p)No (used like the original)
Can be uninitializedYes (dangerous!)No (must initialize)
Use caseOptional params, arrays, dynamic memoryFunction params, aliases

Dynamic Memory (new/delete)

dynamic.cpp
#include <iostream>

int main() {
    // Allocate single object on the heap
    int* p = new int(42);
    std::cout << *p << "
";  // 42
    delete p;  // MUST free memory to avoid leaks
    p = nullptr;  // good practice after delete

    // Allocate array on the heap
    int* arr = new int[5]{1, 2, 3, 4, 5};
    for (int i = 0; i < 5; ++i) {
        std::cout << arr[i] << " ";
    }
    delete[] arr;  // use delete[] for arrays

    // PROBLEM: manual memory is error-prone
    // - Forget delete → memory leak
    // - Delete twice → undefined behavior
    // - Use after delete → undefined behavior
    // SOLUTION: use smart pointers (next section)

    return 0;
}

Smart Pointers (Modern C++)

smart_pointers.cpp
#include <iostream>
#include <memory>
#include <vector>

class Resource {
public:
    Resource(int id) : id_(id) { std::cout << "Created " << id_ << "
"; }
    ~Resource() { std::cout << "Destroyed " << id_ << "
"; }
    void use() { std::cout << "Using " << id_ << "
"; }
private:
    int id_;
};

int main() {
    // unique_ptr — exclusive ownership (most common)
    auto p1 = std::make_unique<Resource>(1);
    p1->use();  // Using 1
    // auto p2 = p1;  // ERROR: cannot copy unique_ptr
    auto p2 = std::move(p1);  // OK: transfer ownership
    // p1 is now nullptr

    // shared_ptr — shared ownership (reference counted)
    auto s1 = std::make_shared<Resource>(2);
    {
        auto s2 = s1;  // both own the resource
        std::cout << "Count: " << s1.use_count() << "
";  // 2
    }  // s2 destroyed, count drops to 1
    std::cout << "Count: " << s1.use_count() << "
";  // 1

    // Resources automatically freed when smart pointers go out of scope
    return 0;
}  // Destroyed 2, Destroyed 1

Best Practices

  • Prefer references for function parameters — they cannot be null and do not need dereferencing.
  • Use const& for read-only parameters to avoid unnecessary copies.
  • Use nullptr instead of NULL or 0 for null pointers (type-safe).
  • Never use raw new/delete in modern C++ — use std::unique_ptr or std::shared_ptr.
  • Use std::make_unique and std::make_shared to create smart pointers (exception-safe, cleaner).
  • Default to unique_ptr — only use shared_ptr when multiple owners genuinely need the same object.
  • Always check pointers for null before dereferencing if they could be null.

Keep Practicing

Use the online compiler to run every example and experiment with modifications. The best way to learn C++ is by writing code — even small programs build strong foundations.