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:
*vpis 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 changeArray 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 nullthis Pointer Summary
| Use Case | Code |
|---|---|
| Resolve name conflict | this->name = name; |
| Return current object | return *this; |
| Self-assignment guard | if (this == &other) return *this; |
| Method chaining | obj.setA(1).setB(2).setC(3); |
| Pass self to function | func(this) or func(*this) |
End of Unit 5.0 — Pointers and Arrays Notes