Introduction to Pointers
Pointers are one of the most powerful features in C++. They store memory addresses rather than values directly, allowing you to manipulate memory, create dynamic data structures, and write highly efficient code.
Pointer
A pointer is a variable that stores the memory address of another variable. Instead of holding data directly, it "points to" where data is stored in memory.
Key Operators: & (address-of) and * (dereference)
Why Use Pointers?
Direct Memory
Access and manipulate memory directly
Data Structures
Build linked lists, trees, graphs
Efficiency
Pass large data without copying
Dynamic Memory
Allocate memory at runtime
Memory Visualization
#include <iostream>
using namespace std;
int main() {
int value = 42; // Variable holding value 42
int* ptr = &value; // Pointer holding address of value
// Memory visualization:
// ┌─────────────────────────────────────────┐
// │ Address │ Variable │ Value │
// ├─────────────────────────────────────────┤
// │ 0x1000 │ value │ 42 │
// │ 0x1008 │ ptr │ 0x1000 (address)│
// └─────────────────────────────────────────┘
cout << "value = " << value << endl; // 42
cout << "Address of value: " << &value << endl; // 0x1000 (example)
cout << "ptr = " << ptr << endl; // 0x1000
cout << "*ptr = " << *ptr << endl; // 42
return 0;
}
Pointer Declaration & Initialization
To declare a pointer, use the asterisk (*) after the data type.
To get the address of a variable, use the address-of operator (&).
#include <iostream>
using namespace std;
int main() {
// Pointer declaration syntax: type* pointerName;
int* intPtr; // Pointer to int
double* dblPtr; // Pointer to double
char* charPtr; // Pointer to char
// Initialize with address of a variable
int num = 100;
intPtr = # // intPtr now holds address of num
// Declare and initialize in one line
int value = 50;
int* ptr = &value;
// Style variations (all equivalent)
int *p1; // * next to variable name
int* p2; // * next to type (preferred)
int * p3; // * with spaces
cout << "num = " << num << endl; // 100
cout << "Address: " << intPtr << endl; // Memory address
cout << "Value at address: " << *intPtr << endl; // 100
return 0;
}
Multiple Pointer Declarations
#include <iostream>
using namespace std;
int main() {
// Careful! Only first variable is a pointer
int* p1, p2; // p1 is int*, p2 is int (NOT a pointer!)
// To declare multiple pointers:
int *ptr1, *ptr2; // Both are int pointers
// Or better, declare separately:
int* ptrA;
int* ptrB;
// Pointer to pointer (double pointer)
int value = 10;
int* ptr = &value;
int** pptr = &ptr; // Pointer to pointer
cout << "value = " << value << endl; // 10
cout << "*ptr = " << *ptr << endl; // 10
cout << "**pptr = " << **pptr << endl; // 10
return 0;
}
Practice Questions: Declaration
Task: Declare a double variable with value 3.14, then create a pointer that points to it.
Show Solution
double pi = 3.14;
double* ptr = π
Task: Create an int variable, a pointer to it, and a pointer to that pointer. Print the value using the double pointer.
Show Solution
int num = 42;
int* ptr = #
int** pptr = &ptr;
cout << **pptr << endl; // 42
Dereferencing Pointers
Dereferencing means accessing the value at the memory address stored in a pointer.
Use the dereference operator (*) to read or modify the value.
#include <iostream>
using namespace std;
int main() {
int num = 25;
int* ptr = #
// Reading value through pointer (dereferencing)
cout << "Value: " << *ptr << endl; // 25
// Modifying value through pointer
*ptr = 100;
cout << "num is now: " << num << endl; // 100
// Both num and *ptr refer to the same memory location
num = 200;
cout << "*ptr is now: " << *ptr << endl; // 200
return 0;
}
The Two Meanings of *
#include <iostream>
using namespace std;
int main() {
// * in declaration: creates a pointer type
int* ptr; // ptr is a pointer to int
int value = 50;
ptr = &value;
// * in expression: dereferences (gets value at address)
cout << *ptr << endl; // 50 (dereferencing)
// Summary:
// int* ptr → Declaration: ptr is a pointer to int
// *ptr → Expression: value at address stored in ptr
// &value → Expression: address of value
return 0;
}
Swapping Values Using Pointers
#include <iostream>
using namespace std;
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
cout << "Before: x=" << x << ", y=" << y << endl;
swap(&x, &y); // Pass addresses
cout << "After: x=" << x << ", y=" << y << endl;
// Output: x=20, y=10
return 0;
}
Practice Questions: Dereferencing
Task: Create an integer with value 5, point to it, and double its value using the pointer.
Show Solution
int num = 5;
int* ptr = #
*ptr = *ptr * 2; // or *ptr *= 2;
cout << num << endl; // 10
Task: Write a function that takes two int pointers and returns a pointer to the larger value.
Show Solution
int* larger(int* a, int* b) {
if (*a > *b) return a;
return b;
}
// Usage:
int x = 10, y = 20;
int* result = larger(&x, &y);
cout << *result << endl; // 20
Pointer Arithmetic
Pointers support arithmetic operations. When you add 1 to a pointer, it moves to the next element of its type (not just one byte). This is essential for working with arrays.
#include <iostream>
using namespace std;
int main() {
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr; // Points to first element
cout << "ptr points to: " << *ptr << endl; // 10
ptr++; // Move to next int (advances by sizeof(int) bytes)
cout << "After ptr++: " << *ptr << endl; // 20
ptr += 2; // Move forward by 2 elements
cout << "After ptr+=2: " << *ptr << endl; // 40
ptr--; // Move back one element
cout << "After ptr--: " << *ptr << endl; // 30
return 0;
}
Pointer Arithmetic Operations
#include <iostream>
using namespace std;
int main() {
int arr[] = {10, 20, 30, 40, 50};
int* p1 = &arr[0];
int* p2 = &arr[3];
// Pointer + integer: moves pointer forward
cout << *(p1 + 2) << endl; // 30 (arr[0+2])
// Pointer - integer: moves pointer backward
cout << *(p2 - 1) << endl; // 30 (arr[3-1])
// Pointer - pointer: number of elements between them
cout << (p2 - p1) << endl; // 3
// Comparison
if (p1 < p2) {
cout << "p1 comes before p2" << endl;
}
// Size matters!
cout << "Size of int: " << sizeof(int) << " bytes" << endl;
// ptr+1 moves by sizeof(int) bytes, not 1 byte
return 0;
}
Iterating with Pointer Arithmetic
#include <iostream>
using namespace std;
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
// Method 1: Index notation
cout << "Using index: ";
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
// Method 2: Pointer arithmetic
cout << "Using pointer: ";
for (int* p = arr; p < arr + size; p++) {
cout << *p << " ";
}
cout << endl;
// Method 3: Pointer with offset
cout << "Using offset: ";
int* ptr = arr;
for (int i = 0; i < size; i++) {
cout << *(ptr + i) << " ";
}
cout << endl;
return 0;
}
Practice Questions: Pointer Arithmetic
Task: Given an array int arr[] = {5, 10, 15, 20, 25};, access the third element (15) using pointer arithmetic.
Show Solution
int arr[] = {5, 10, 15, 20, 25};
int* ptr = arr;
cout << *(ptr + 2) << endl; // 15
Task: Write a function that calculates the sum of an array using only pointer arithmetic (no index notation).
Show Solution
int sumArray(int* arr, int size) {
int sum = 0;
int* end = arr + size;
for (int* p = arr; p < end; p++) {
sum += *p;
}
return sum;
}
// Usage:
int arr[] = {1, 2, 3, 4, 5};
cout << sumArray(arr, 5) << endl; // 15
Pointers & Arrays
Arrays and pointers are closely related in C++. An array name can be used as a pointer to its first element, and pointer notation can access array elements.
#include <iostream>
using namespace std;
int main() {
int arr[] = {10, 20, 30, 40, 50};
// Array name IS a pointer to first element
cout << "arr = " << arr << endl; // Address
cout << "&arr[0] = " << &arr[0] << endl; // Same address!
// These are equivalent:
cout << arr[0] << endl; // 10 (index notation)
cout << *arr << endl; // 10 (pointer notation)
cout << *(arr+0) << endl; // 10 (explicit)
// These are also equivalent:
cout << arr[2] << endl; // 30
cout << *(arr+2) << endl; // 30
// Important: arr is a CONSTANT pointer
// arr = arr + 1; // Error! Can't modify array name
// But you can use a pointer variable:
int* ptr = arr;
ptr++; // OK - ptr can be modified
cout << *ptr << endl; // 20
return 0;
}
Array vs Pointer Differences
#include <iostream>
using namespace std;
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;
// sizeof difference
cout << "sizeof(arr): " << sizeof(arr) << endl; // 20 (5 * 4 bytes)
cout << "sizeof(ptr): " << sizeof(ptr) << endl; // 8 (pointer size)
// Can modify ptr, not arr
ptr = ptr + 1; // OK
// arr = arr + 1; // Error!
// Both can be used with [] operator
cout << ptr[0] << endl; // 2 (ptr now points to arr[1])
cout << arr[0] << endl; // 1
return 0;
}
Passing Arrays to Functions
#include <iostream>
using namespace std;
// Arrays decay to pointers when passed to functions
// These declarations are equivalent:
void printArray(int arr[], int size);
void printArray(int* arr, int size);
void printArray(int* arr, int size) {
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
// or: cout << *(arr + i) << " ";
}
cout << endl;
}
void doubleElements(int* arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // Modifies original array!
}
}
int main() {
int nums[] = {1, 2, 3, 4, 5};
int size = sizeof(nums) / sizeof(nums[0]);
printArray(nums, size); // 1 2 3 4 5
doubleElements(nums, size);
printArray(nums, size); // 2 4 6 8 10
return 0;
}
Practice Questions: Pointers & Arrays
Task: Print an array in reverse order using pointer arithmetic.
Show Solution
int arr[] = {1, 2, 3, 4, 5};
int size = 5;
for (int* p = arr + size - 1; p >= arr; p--) {
cout << *p << " ";
}
// Output: 5 4 3 2 1
Task: Write a function that reverses an array in-place using two pointers.
Show Solution
void reverseArray(int* arr, int size) {
int* start = arr;
int* end = arr + size - 1;
while (start < end) {
// Swap
int temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
}
// Usage:
int arr[] = {1, 2, 3, 4, 5};
reverseArray(arr, 5);
// arr is now {5, 4, 3, 2, 1}
Pointers & Functions
Pointers allow functions to modify variables from the caller, return multiple values, and work with dynamic data. You can also create pointers to functions for callbacks and flexible code.
Returning Multiple Values
#include <iostream>
using namespace std;
// Return multiple values through pointer parameters
void getMinMax(int arr[], int size, int* min, int* max) {
*min = *max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] < *min) *min = arr[i];
if (arr[i] > *max) *max = arr[i];
}
}
void divideWithRemainder(int dividend, int divisor, int* quotient, int* remainder) {
*quotient = dividend / divisor;
*remainder = dividend % divisor;
}
int main() {
int arr[] = {3, 1, 4, 1, 5, 9, 2, 6};
int minVal, maxVal;
getMinMax(arr, 8, &minVal, &maxVal);
cout << "Min: " << minVal << ", Max: " << maxVal << endl;
// Output: Min: 1, Max: 9
int q, r;
divideWithRemainder(17, 5, &q, &r);
cout << "17 / 5 = " << q << " remainder " << r << endl;
// Output: 17 / 5 = 3 remainder 2
return 0;
}
Function Pointers
#include <iostream>
using namespace std;
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() {
// Declare function pointer
// Syntax: return_type (*pointer_name)(parameter_types)
int (*operation)(int, int);
// Point to different functions
operation = add;
cout << "5 + 3 = " << operation(5, 3) << endl; // 8
operation = subtract;
cout << "5 - 3 = " << operation(5, 3) << endl; // 2
operation = multiply;
cout << "5 * 3 = " << operation(5, 3) << endl; // 15
// Array of function pointers
int (*ops[3])(int, int) = {add, subtract, multiply};
cout << "Using array: " << ops[0](10, 5) << endl; // 15
return 0;
}
Callback Functions
#include <iostream>
using namespace std;
// Callback function type
typedef bool (*Predicate)(int);
// Filter function using callback
void filterArray(int arr[], int size, Predicate condition) {
cout << "Filtered: ";
for (int i = 0; i < size; i++) {
if (condition(arr[i])) {
cout << arr[i] << " ";
}
}
cout << endl;
}
// Predicate functions
bool isEven(int n) { return n % 2 == 0; }
bool isPositive(int n) { return n > 0; }
bool isGreaterThan5(int n) { return n > 5; }
int main() {
int arr[] = {-2, 1, 4, 7, -3, 8, 6, 2};
int size = 8;
filterArray(arr, size, isEven); // -2 4 8 6 2
filterArray(arr, size, isPositive); // 1 4 7 8 6 2
filterArray(arr, size, isGreaterThan5); // 7 8 6
return 0;
}
Practice Questions: Pointers & Functions
Task: Write a function that takes two numbers and returns both their sum and product through pointer parameters.
Show Solution
void sumAndProduct(int a, int b, int* sum, int* product) {
*sum = a + b;
*product = a * b;
}
// Usage:
int s, p;
sumAndProduct(4, 5, &s, &p);
cout << "Sum: " << s << ", Product: " << p << endl;
// Sum: 9, Product: 20
Task: Create a calculate function that takes two numbers, an operator character, and uses function pointers to perform the operation.
Show Solution
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return b != 0 ? a / b : 0; }
int calculate(int a, int b, char op) {
int (*operation)(int, int);
switch(op) {
case '+': operation = add; break;
case '-': operation = sub; break;
case '*': operation = mul; break;
case '/': operation = div; break;
default: return 0;
}
return operation(a, b);
}
// Usage:
cout << calculate(10, 5, '+') << endl; // 15
cout << calculate(10, 5, '*') << endl; // 50
Dynamic Memory Allocation
Dynamic memory allocation lets you request memory at runtime using new
and release it with delete. This is essential for creating data
structures whose size is unknown at compile time.
#include <iostream>
using namespace std;
int main() {
// Allocate single variable
int* ptr = new int; // Allocate memory for one int
*ptr = 42;
cout << "Value: " << *ptr << endl; // 42
delete ptr; // Free memory
ptr = nullptr; // Good practice: avoid dangling pointer
// Allocate with initialization
int* num = new int(100); // Initialize to 100
cout << "Initialized: " << *num << endl; // 100
delete num;
// Allocate array
int size = 5;
int* arr = new int[size]; // Allocate array of 5 ints
for (int i = 0; i < size; i++) {
arr[i] = (i + 1) * 10;
}
for (int i = 0; i < size; i++) {
cout << arr[i] << " "; // 10 20 30 40 50
}
cout << endl;
delete[] arr; // Use delete[] for arrays!
arr = nullptr;
return 0;
}
new vs delete Rules
| Allocation | Deallocation | Example |
|---|---|---|
new T |
delete ptr |
int* p = new int; delete p; |
new T[n] |
delete[] ptr |
int* a = new int[5]; delete[] a; |
Dynamic 2D Array
#include <iostream>
using namespace std;
int main() {
int rows = 3, cols = 4;
// Allocate 2D array (array of pointers)
int** matrix = new int*[rows];
for (int i = 0; i < rows; i++) {
matrix[i] = new int[cols];
}
// Initialize
int value = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = value++;
}
}
// Print
cout << "Matrix:" << endl;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cout << matrix[i][j] << "\t";
}
cout << endl;
}
// Deallocate (reverse order!)
for (int i = 0; i < rows; i++) {
delete[] matrix[i];
}
delete[] matrix;
return 0;
}
- Every
newmust have a matchingdelete - Every
new[]must have a matchingdelete[] - Never delete the same memory twice (double-free)
- Set pointers to
nullptrafter deleting - Memory leaks occur when you forget to delete
Practice Questions: Dynamic Memory
Task: Ask user for array size, dynamically allocate, fill with values 1 to n, print, and properly deallocate.
Show Solution
int size;
cout << "Enter size: ";
cin >> size;
int* arr = new int[size];
for (int i = 0; i < size; i++) {
arr[i] = i + 1;
}
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
delete[] arr;
arr = nullptr;
Task: Write a function that takes a dynamic array and its size, and returns a new array with double the size (copying old elements).
Show Solution
int* resizeArray(int* oldArr, int oldSize, int newSize) {
int* newArr = new int[newSize];
// Copy old elements
int copySize = (oldSize < newSize) ? oldSize : newSize;
for (int i = 0; i < copySize; i++) {
newArr[i] = oldArr[i];
}
// Initialize remaining elements to 0
for (int i = copySize; i < newSize; i++) {
newArr[i] = 0;
}
delete[] oldArr; // Free old array
return newArr;
}
// Usage:
int* arr = new int[3]{1, 2, 3};
arr = resizeArray(arr, 3, 6);
// arr is now size 6: {1, 2, 3, 0, 0, 0}
Null & Void Pointers
Null pointers indicate "no address" and void pointers can point to any type. Understanding these is crucial for safe pointer usage.
Null Pointers
#include <iostream>
using namespace std;
int main() {
// Null pointer - points to nothing
int* ptr1 = nullptr; // C++11 way (preferred)
int* ptr2 = NULL; // C-style (older)
int* ptr3 = 0; // Also works but not recommended
// Always check before dereferencing!
if (ptr1 != nullptr) {
cout << *ptr1 << endl;
} else {
cout << "ptr1 is null" << endl;
}
// After delete, set to nullptr
int* num = new int(42);
delete num;
num = nullptr; // Prevents accidental use of dangling pointer
// Check if allocation succeeded
int* bigArray = new(nothrow) int[1000000000];
if (bigArray == nullptr) {
cout << "Allocation failed!" << endl;
}
return 0;
}
Void Pointers
#include <iostream>
using namespace std;
int main() {
// void* can point to any type
int num = 10;
double dbl = 3.14;
char ch = 'A';
void* ptr;
ptr = #
cout << "int: " << *(static_cast<int*>(ptr)) << endl; // 10
ptr = &dbl;
cout << "double: " << *(static_cast<double*>(ptr)) << endl; // 3.14
ptr = &ch;
cout << "char: " << *(static_cast<char*>(ptr)) << endl; // A
// Note: Cannot dereference void* directly
// cout << *ptr; // Error!
// Must cast to specific type first
return 0;
}
Common Pointer Pitfalls
#include <iostream>
using namespace std;
// Pitfall 1: Uninitialized pointer
void pitfall1() {
int* ptr; // Garbage value!
// *ptr = 10; // Undefined behavior!
// Fix: Initialize to nullptr or valid address
int* safe = nullptr;
}
// Pitfall 2: Dangling pointer
void pitfall2() {
int* ptr = new int(42);
delete ptr;
// *ptr = 10; // Dangling pointer! Undefined behavior!
// Fix: Set to nullptr after delete
ptr = nullptr;
}
// Pitfall 3: Memory leak
void pitfall3() {
int* ptr = new int(42);
ptr = new int(100); // Lost reference to first allocation!
// Memory leak: can't delete first int
// Fix: Delete before reassigning
// delete ptr;
// ptr = new int(100);
}
// Pitfall 4: Returning address of local variable
int* pitfall4() {
int local = 42;
return &local; // Disaster! local is destroyed after function
}
int main() {
// Don't call these - they demonstrate bad practices
return 0;
}
Practice Questions: Null & Void Pointers
Task: Write a function that safely prints the value of an int pointer, printing "null" if the pointer is null.
Show Solution
void safePrint(int* ptr) {
if (ptr != nullptr) {
cout << *ptr << endl;
} else {
cout << "null" << endl;
}
}
// Usage:
int x = 42;
int* ptr1 = &x;
int* ptr2 = nullptr;
safePrint(ptr1); // 42
safePrint(ptr2); // null
Task: Write a generic swap function using void pointers that can swap values of any type.
Show Solution
void genericSwap(void* a, void* b, size_t size) {
char* temp = new char[size];
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
delete[] temp;
}
// Usage:
int x = 10, y = 20;
genericSwap(&x, &y, sizeof(int));
// x = 20, y = 10
double d1 = 1.5, d2 = 2.5;
genericSwap(&d1, &d2, sizeof(double));
// d1 = 2.5, d2 = 1.5
Key Takeaways
Pointer Basics
Store addresses with *, get address with &
Dereferencing
*ptr accesses value at address
Pointer Arithmetic
ptr+1 moves by sizeof(type) bytes
Arrays & Pointers
Array name is pointer to first element
Dynamic Memory
new allocates, delete frees memory
Safety
Check nullptr, avoid dangling pointers
Knowledge Check
Quick Quiz
Test what you have learned about C++ pointers