Unit 5.0: Pointers and Arrays 🎯

1. Introduction to Pointers

  • A pointer is a variable that stores the memory address of another variable.

Memory Model

Variable:   int x = 42;
Memory:     Address 0x1000 → value 42

Pointer:    int* ptr = &x;
Memory:     Address 0x2000 → value 0x1000 (stores address of x)

Basic Pointer Operations

#include <iostream>
using namespace std;
 
int main() {
    int x = 42;
 
    int* ptr = &x;       // & = address-of operator
 
    cout << "Value of x:    " << x    << endl;    // 42
    cout << "Address of x:  " << &x   << endl;    // 0x... (some hex address)
    cout << "ptr holds:     " << ptr  << endl;    // same hex address
    cout << "Value via ptr: " << *ptr << endl;    // 42 (* = dereference)
 
    *ptr = 100;    // modify x through pointer
    cout << "x after *ptr=100: " << x << endl;   // 100
 
    // Pointer arithmetic
    int arr[] = {10, 20, 30, 40, 50};
    int* p = arr;    // points to arr[0]
 
    cout << *p     << endl;   // 10
    cout << *(p+1) << endl;   // 20 (next element)
    cout << *(p+4) << endl;   // 50
 
    p++;    // move pointer to next element
    cout << *p << endl;       // 20
 
    return 0;
}

Pointer Types

#include <iostream>
using namespace std;
 
int main() {
    int    i  = 10;    int*    ip = &i;
    double d  = 3.14;  double* dp = &d;
    char   c  = 'A';   char*   cp = &c;
    bool   b  = true;  bool*   bp = &b;
 
    cout << *ip << endl;   // 10
    cout << *dp << endl;   // 3.14
    cout << *cp << endl;   // A
    cout << *bp << endl;   // 1
 
    // Null pointer (pointer to nothing)
    int* nullPtr = nullptr;    // C++11: use nullptr (not NULL or 0)
 
    if (nullPtr == nullptr) {
        cout << "Pointer is null — safe!" << endl;
    }
 
    // Pointer to pointer
    int val = 5;
    int*  p1 = &val;
    int** p2 = &p1;   // pointer to a pointer
 
    cout << **p2 << endl;   // 5 (double dereference)
    return 0;
}

2. Void Pointers

A void* pointer can hold the address of any type, but must be cast before dereferencing.

#include <iostream>
using namespace std;
 
void printValue(void* ptr, char type) {
    switch (type) {
        case 'i': cout << "int: "    << *static_cast<int*>(ptr)    << endl; break;
        case 'd': cout << "double: " << *static_cast<double*>(ptr) << endl; break;
        case 'c': cout << "char: "   << *static_cast<char*>(ptr)   << endl; break;
    }
}
 
int main() {
    int    i = 42;
    double d = 3.14;
    char   c = 'Z';
 
    void* vp;
 
    vp = &i;  printValue(vp, 'i');   // int: 42
    vp = &d;  printValue(vp, 'd');   // double: 3.14
    vp = &c;  printValue(vp, 'c');   // char: Z
 
    // Void pointer with dynamic memory
    void* block = operator new(sizeof(int) * 5);
    int* arr = static_cast<int*>(block);
    for (int k = 0; k < 5; k++) arr[k] = k * 10;
    for (int k = 0; k < 5; k++) cout << arr[k] << " ";
    cout << endl;
    operator delete(block);
 
    return 0;
}

Generic Swap Using void*

#include <iostream>
#include <cstring>    // for memcpy
using namespace std;
 
void genericSwap(void* a, void* b, size_t size) {
    void* temp = operator new(size);
    memcpy(temp, a, size);
    memcpy(a, b, size);
    memcpy(b, temp, size);
    operator delete(temp);
}
 
int main() {
    int x = 10, y = 20;
    genericSwap(&x, &y, sizeof(int));
    cout << x << " " << y << endl;   // 20 10
 
    double a = 1.1, b = 2.2;
    genericSwap(&a, &b, sizeof(double));
    cout << a << " " << b << endl;   // 2.2 1.1
    return 0;
}

Key Rules for void Pointers:

  • Cannot dereference directly: *vp is an error
  • Cannot do arithmetic: vp++ is an error
  • Must cast to a typed pointer before use
  • Useful for generic/low-level programming

3. Pointer to Class

A pointer can point to a class type just like any other type. Use -> (arrow operator) to access members through a pointer.

#include <iostream>
#include <string>
using namespace std;
 
class Book {
public:
    string title;
    string author;
    double price;
 
    Book(string t, string a, double p)
        : title(t), author(a), price(p) {}
 
    void display() const {
        cout << "\"" << title << "\" by " << author
             << " — $" << price << endl;
    }
 
    void applyDiscount(double percent) {
        price -= price * (percent / 100.0);
    }
};
 
int main() {
    Book b1("C++ Primer", "Lippman", 59.99);
 
    // Pointer to a class object
    Book* ptr = &b1;
 
    // Two ways to access members via pointer
    cout << (*ptr).title << endl;   // dereference then dot
    cout << ptr->title << endl;     // arrow operator (preferred)
 
    ptr->display();
    ptr->applyDiscount(10);         // 10% off
    ptr->display();                 // price updated
 
    // Pointer to dynamically allocated object
    Book* bPtr = new Book("Effective C++", "Meyers", 45.00);
    bPtr->display();
    delete bPtr;    // must free!
    bPtr = nullptr;
 
    return 0;
}

Array of Class Pointers

#include <iostream>
#include <string>
using namespace std;
 
class Student {
public:
    string name;
    int marks;
    Student(string n, int m) : name(n), marks(m) {}
    void display() const {
        cout << name << ": " << marks << endl;
    }
};
 
int main() {
    const int N = 4;
    Student* students[N];
 
    students[0] = new Student("Alice", 92);
    students[1] = new Student("Bob", 78);
    students[2] = new Student("Carol", 85);
    students[3] = new Student("Dave", 91);
 
    // Find top scorer
    int topIdx = 0;
    for (int i = 1; i < N; i++) {
        if (students[i]->marks > students[topIdx]->marks)
            topIdx = i;
    }
 
    cout << "Top scorer: ";
    students[topIdx]->display();
 
    for (int i = 0; i < N; i++) {
        delete students[i];
        students[i] = nullptr;
    }
    return 0;
}

4. Pointer to Object

Detailed look at pointer-to-object operations and dynamic object creation.

Stack vs Heap Objects

#include <iostream>
using namespace std;
 
class Point {
public:
    int x, y;
    Point(int x = 0, int y = 0) : x(x), y(y) {
        cout << "Point(" << x << "," << y << ") created" << endl;
    }
    ~Point() {
        cout << "Point(" << x << "," << y << ") destroyed" << endl;
    }
    void display() const { cout << "(" << x << "," << y << ")" << endl; }
};
 
int main() {
    // Stack object: destroyed automatically at end of scope
    {
        Point p1(1, 2);     // constructor called
        p1.display();
    }   // p1 destructor called HERE automatically
 
    cout << "--- After block ---" << endl;
 
    // Heap object: YOU must destroy it
    Point* p2 = new Point(3, 4);   // constructor called
    p2->display();
    delete p2;                      // destructor called explicitly
    p2 = nullptr;                   // good practice
 
    // Array of heap objects
    int n = 3;
    Point* arr = new Point[n];      // calls default constructor 3 times
    arr[0] = Point(10, 10);
    arr[1] = Point(20, 20);
    arr[2] = Point(30, 30);
 
    for (int i = 0; i < n; i++) arr[i].display();
    delete[] arr;    // must use delete[] for arrays!
 
    return 0;
}

Pointer to Member Functions

#include <iostream>
using namespace std;
 
class Calculator {
public:
    int add(int a, int b)      { return a + b; }
    int subtract(int a, int b) { return a - b; }
    int multiply(int a, int b) { return a * b; }
};
 
int main() {
    Calculator calc;
 
    // Pointer to member function syntax: ReturnType (ClassName::*ptrName)(params)
    int (Calculator::*funcPtr)(int, int);
 
    funcPtr = &Calculator::add;
    cout << (calc.*funcPtr)(10, 5) << endl;    // 15
 
    funcPtr = &Calculator::subtract;
    cout << (calc.*funcPtr)(10, 5) << endl;    // 5
 
    funcPtr = &Calculator::multiply;
    cout << (calc.*funcPtr)(10, 5) << endl;    // 50
 
    // Via pointer to object
    Calculator* cPtr = &calc;
    funcPtr = &Calculator::add;
    cout << (cPtr->*funcPtr)(7, 3) << endl;    // 10
 
    return 0;
}

5. this Pointer

this is an implicit pointer available inside every non-static member function. It points to the current object that called the function.

Basic this Usage

#include <iostream>
#include <string>
using namespace std;
 
class Person {
    string name;
    int age;
 
public:
    // 'this' resolves naming conflict between parameter and member
    Person(string name, int age) {
        this->name = name;   // this->name = member, name = parameter
        this->age  = age;
    }
 
    // Return *this for method chaining
    Person& setName(string name) {
        this->name = name;
        return *this;       // return current object
    }
 
    Person& setAge(int age) {
        this->age = age;
        return *this;
    }
 
    void display() const {
        cout << name << " (age " << age << ")" << endl;
    }
 
    // Compare with another object
    bool isOlder(const Person& other) const {
        return this->age > other.age;
    }
 
    // Self-assignment check
    Person& operator=(const Person& other) {
        if (this == &other) return *this;   // guard against self-assignment
        this->name = other.name;
        this->age  = other.age;
        return *this;
    }
};
 
int main() {
    Person p1("Alice", 25);
    p1.display();
 
    // Method chaining using *this
    Person p2("", 0);
    p2.setName("Bob").setAge(30).display();   // chained calls
 
    cout << (p1.isOlder(p2) ? "Alice" : "Bob") << " is older" << endl;
 
    return 0;
}

this in Builder Pattern

#include <iostream>
#include <string>
using namespace std;
 
class QueryBuilder {
    string table;
    string conditions;
    string columns;
    int limitVal;
 
public:
    QueryBuilder() : columns("*"), limitVal(-1) {}
 
    QueryBuilder& from(string t)   { table = t;       return *this; }
    QueryBuilder& select(string c) { columns = c;     return *this; }
    QueryBuilder& where(string w)  { conditions = w;  return *this; }
    QueryBuilder& limit(int n)     { limitVal = n;    return *this; }
 
    string build() const {
        string q = "SELECT " + columns + " FROM " + table;
        if (!conditions.empty()) q += " WHERE " + conditions;
        if (limitVal > 0) q += " LIMIT " + to_string(limitVal);
        return q;
    }
};
 
int main() {
    // Fluent interface — all returns *this
    string query = QueryBuilder()
        .from("students")
        .select("name, marks")
        .where("marks > 80")
        .limit(10)
        .build();
 
    cout << query << endl;
    // SELECT name, marks FROM students WHERE marks > 80 LIMIT 10
    return 0;
}

6. Arrays in C++

An array stores a fixed number of elements of the same type in contiguous memory.

1D Arrays

#include <iostream>
using namespace std;
 
int main() {
    // Declaration and initialization
    int arr1[5];                          // uninitialized
    int arr2[5] = {10, 20, 30, 40, 50};  // fully initialized
    int arr3[]  = {1, 2, 3, 4, 5};       // size inferred (5)
    int arr4[5] = {1, 2};                 // rest are 0: {1,2,0,0,0}
    int arr5[5] = {};                     // all zeros
 
    // Access
    cout << arr2[0] << endl;    // 10 (first)
    cout << arr2[4] << endl;    // 50 (last)
 
    // Modify
    arr2[2] = 99;
 
    // Traverse
    int n = 5;
    for (int i = 0; i < n; i++) {
        cout << arr2[i] << " ";
    }
    cout << endl;
 
    // Range-based for (C++11)
    for (int x : arr2) {
        cout << x << " ";
    }
    cout << endl;
 
    // Size of array
    int size = sizeof(arr3) / sizeof(arr3[0]);
    cout << "Size: " << size << endl;    // 5
 
    return 0;
}

Array Algorithms

#include <iostream>
#include <algorithm>    // for sort, reverse, etc.
using namespace std;
 
int main() {
    int arr[] = {64, 25, 12, 22, 11, 90, 45};
    int n = sizeof(arr) / sizeof(arr[0]);
 
    // Sum and average
    int sum = 0;
    for (int x : arr) sum += x;
    double avg = (double)sum / n;
    cout << "Sum: " << sum << ", Avg: " << avg << endl;
 
    // Min and Max
    int minVal = arr[0], maxVal = arr[0];
    for (int x : arr) {
        if (x < minVal) minVal = x;
        if (x > maxVal) maxVal = x;
    }
    cout << "Min: " << minVal << ", Max: " << maxVal << endl;
 
    // Sort (using standard library)
    sort(arr, arr + n);
    cout << "Sorted: ";
    for (int x : arr) cout << x << " ";
    cout << endl;
 
    // Reverse
    reverse(arr, arr + n);
    cout << "Reversed: ";
    for (int x : arr) cout << x << " ";
    cout << endl;
 
    return 0;
}

2D Arrays

#include <iostream>
using namespace std;
 
int main() {
    int matrix[3][4] = {
        {1,  2,  3,  4},
        {5,  6,  7,  8},
        {9, 10, 11, 12}
    };
 
    int rows = 3, cols = 4;
 
    // Print
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            cout << matrix[i][j] << "\t";
        }
        cout << endl;
    }
 
    // Sum of each row
    for (int i = 0; i < rows; i++) {
        int rowSum = 0;
        for (int j = 0; j < cols; j++) rowSum += matrix[i][j];
        cout << "Row " << i << " sum: " << rowSum << endl;
    }
 
    // Matrix transpose
    int trans[4][3];
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            trans[j][i] = matrix[i][j];
 
    cout << "Transpose:" << endl;
    for (int i = 0; i < cols; i++) {
        for (int j = 0; j < rows; j++)
            cout << trans[i][j] << "\t";
        cout << endl;
    }
 
    return 0;
}

Passing Arrays to Functions

#include <iostream>
using namespace std;
 
// Array decays to pointer when passed to function
void printArray(int arr[], int n) {
    for (int i = 0; i < n; i++) cout << arr[i] << " ";
    cout << endl;
}
 
// Using const to protect the array
double average(const int arr[], int n) {
    int sum = 0;
    for (int i = 0; i < n; i++) sum += arr[i];
    return (double)sum / n;
}
 
// Passing 2D array (must specify all dimensions except first)
void print2D(int arr[][3], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 3; j++)
            cout << arr[i][j] << " ";
        cout << endl;
    }
}
 
int main() {
    int data[] = {5, 3, 8, 1, 9};
    printArray(data, 5);
    cout << "Average: " << average(data, 5) << endl;
 
    int mat[2][3] = {{1,2,3},{4,5,6}};
    print2D(mat, 2);
    return 0;
}

7. Pointer and Array Relationship

Arrays and pointers are closely related in C++. An array name is essentially a constant pointer to its first element.

#include <iostream>
using namespace std;
 
int main() {
    int arr[] = {10, 20, 30, 40, 50};
 
    // Array name = pointer to first element
    int* ptr = arr;   // same as int* ptr = &arr[0]
 
    // These are all equivalent
    cout << arr[2]   << endl;   // 30 (index notation)
    cout << *(arr+2) << endl;   // 30 (pointer arithmetic)
    cout << ptr[2]   << endl;   // 30 (pointer with index)
    cout << *(ptr+2) << endl;   // 30 (pointer arithmetic)
 
    // Traversal using pointer
    int* p = arr;
    int* end = arr + 5;
 
    while (p != end) {
        cout << *p << " ";
        p++;
    }
    cout << endl;
 
    // Difference between array and pointer
    cout << sizeof(arr) << endl;   // 20 (5 * 4 bytes) — full array size
    cout << sizeof(ptr) << endl;   // 8  (size of a pointer on 64-bit)
 
    // arr = ptr;  // ERROR: array name is a constant pointer!
    ptr = arr + 2;   // pointer can be reassigned
    cout << *ptr << endl;   // 30
 
    return 0;
}

Pointer Arithmetic Details

#include <iostream>
using namespace std;
 
int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int* p = arr;
 
    // p + n moves forward by n * sizeof(int) bytes
    cout << *(p)   << endl;  // arr[0] = 1
    cout << *(p+1) << endl;  // arr[1] = 2
    cout << *(p+4) << endl;  // arr[4] = 5
 
    // Pointer difference (gives number of elements between)
    int* first = &arr[0];
    int* last  = &arr[4];
    cout << "Distance: " << (last - first) << endl;  // 4
 
    // Comparison
    if (first < last)
        cout << "first comes before last" << endl;
 
    // Iterating
    for (int* q = arr; q < arr + 5; q++) {
        cout << *q << " ";
    }
    cout << endl;
 
    return 0;
}

8. Dynamic Memory with Pointers and Arrays

Dynamic arrays are created at runtime using new and freed with delete.

#include <iostream>
using namespace std;
 
int main() {
    int n;
    cout << "Enter size: ";
    cin >> n;
 
    // Dynamically allocate array of size n
    int* dynArr = new int[n];
 
    // Fill
    for (int i = 0; i < n; i++) dynArr[i] = (i + 1) * 10;
 
    // Use
    for (int i = 0; i < n; i++) cout << dynArr[i] << " ";
    cout << endl;
 
    // MUST free with delete[]
    delete[] dynArr;
    dynArr = nullptr;
 
    // Dynamic 2D array
    int rows = 3, cols = 4;
    int** matrix = new int*[rows];
    for (int i = 0; i < rows; i++)
        matrix[i] = new int[cols];
 
    // Fill and print
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            matrix[i][j] = i * cols + j;
 
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++)
            cout << matrix[i][j] << "\t";
        cout << endl;
    }
 
    // Free 2D dynamic array
    for (int i = 0; i < rows; i++) delete[] matrix[i];
    delete[] matrix;
    matrix = nullptr;
 
    return 0;
}

9. Smart Pointers (Modern C++)

Smart pointers automatically manage memory — no manual delete needed.

unique_ptr — Exclusive Ownership

#include <iostream>
#include <memory>
#include <string>
using namespace std;
 
class Resource {
    string name;
public:
    Resource(string n) : name(n) {
        cout << "Resource \"" << name << "\" acquired" << endl;
    }
    ~Resource() {
        cout << "Resource \"" << name << "\" released" << endl;
    }
    void use() const { cout << "Using: " << name << endl; }
};
 
int main() {
    // unique_ptr: only ONE owner at a time
    unique_ptr<Resource> ptr1 = make_unique<Resource>("Database");
    ptr1->use();
 
    // Transfer ownership (move, not copy)
    unique_ptr<Resource> ptr2 = move(ptr1);
    // ptr1 is now nullptr
    ptr2->use();
 
    // Array with unique_ptr
    unique_ptr<int[]> arr = make_unique<int[]>(5);
    for (int i = 0; i < 5; i++) arr[i] = i * 10;
    for (int i = 0; i < 5; i++) cout << arr[i] << " ";
    cout << endl;
 
    // Automatic cleanup when ptr2 goes out of scope
    return 0;
}

shared_ptr — Shared Ownership

#include <iostream>
#include <memory>
using namespace std;
 
class Node {
public:
    int value;
    Node(int v) : value(v) {
        cout << "Node " << v << " created" << endl;
    }
    ~Node() {
        cout << "Node " << value << " destroyed" << endl;
    }
};
 
int main() {
    shared_ptr<Node> sp1 = make_shared<Node>(42);
    cout << "Count: " << sp1.use_count() << endl;   // 1
 
    {
        shared_ptr<Node> sp2 = sp1;    // shared ownership
        cout << "Count: " << sp1.use_count() << endl;   // 2
        sp2->value = 100;
        cout << sp1->value << endl;    // 100 (same object!)
    }   // sp2 destroyed, count drops to 1
 
    cout << "Count: " << sp1.use_count() << endl;   // 1
    // Node destroyed when sp1 goes out of scope (count → 0)
    return 0;
}

Smart Pointers with Classes

#include <iostream>
#include <memory>
#include <vector>
using namespace std;
 
class Animal {
public:
    virtual void sound() const = 0;
    virtual ~Animal() {}
};
 
class Dog : public Animal {
public:
    void sound() const override { cout << "Woof!" << endl; }
};
 
class Cat : public Animal {
public:
    void sound() const override { cout << "Meow!" << endl; }
};
 
int main() {
    // Polymorphism with smart pointers
    vector<unique_ptr<Animal>> zoo;
    zoo.push_back(make_unique<Dog>());
    zoo.push_back(make_unique<Cat>());
    zoo.push_back(make_unique<Dog>());
 
    for (const auto& a : zoo) {
        a->sound();    // correct virtual dispatch
    }
    // All memory freed automatically!
    return 0;
}

Quick Reference Summary

Pointer Cheat Sheet

int x = 10;
int* ptr = &x;      // ptr stores address of x
*ptr = 20;          // dereference: modify x via pointer
ptr++;              // pointer arithmetic: move to next int
 
// Null pointer
int* np = nullptr;  // always initialize!
 
// Pointer to const
const int* cp = &x;  // cannot change *cp
*cp = 5;             // ERROR
 
// Const pointer
int* const cp2 = &x; // cannot change cp2 itself
cp2 = &y;            // ERROR
 
// Const pointer to const
const int* const cp3 = &x;  // neither can change

Array Cheat Sheet

int arr[5] = {1,2,3,4,5};   // stack array
int* dyn = new int[n];        // heap array — delete[] when done
 
arr[i]       // access
*(arr + i)   // equivalent pointer syntax
 
sizeof(arr)/sizeof(arr[0])  // get array length (only for stack arrays)

Smart Pointer Cheat Sheet

#include <memory>
 
// unique_ptr: one owner
auto up = make_unique<Type>(args);
up->method();
 
// shared_ptr: multiple owners
auto sp = make_shared<Type>(args);
sp.use_count();   // number of owners
 
// Transfer unique_ptr ownership
auto up2 = move(up);   // up is now null

this Pointer Summary

Use CaseCode
Resolve name conflictthis->name = name;
Return current objectreturn *this;
Self-assignment guardif (this == &other) return *this;
Method chainingobj.setA(1).setB(2).setC(3);
Pass self to functionfunc(this) or func(*this)

End of Unit 5.0 — Pointers and Arrays Notes