C++ Pointers: Complete Guide to All Pointer Types
Master C++ pointers with comprehensive examples of all pointer types, memory management, smart pointers, pointer arithmetic, and practical applications. Learn efficient memory manipulation techniques.
Basic Pointers
Memory addresses
Advanced Pointers
Complex types
Smart Pointers
Memory safety
Pointer Arithmetic
Memory navigation
Understanding Pointers in C++
Pointers are powerful features of C++ that store memory addresses. They enable direct memory access, dynamic memory allocation, and efficient data manipulation. Mastering pointers is essential for advanced C++ programming.
Basic Pointer Memory Address
Stores memory address of a variable. Foundation of pointer concepts.
Advanced Pointers Complex Types
Pointers to pointers, function pointers, void pointers for flexibility.
Smart Pointers Automatic Management
Automatically manage memory, prevent leaks (C++11 and later).
Pointer Arithmetic
Navigate through memory using arithmetic operations on pointers.
Pointer Concept Visualization
int x = 42;Value: 42
Address:
0x7fff5fbff8ac
int* ptr = &x;Stores:
0x7fff5fbff8acPoints to: 42
Pointer stores memory address: ptr contains the address of x, allowing indirect access to its value.
Key Concepts:
- Address Operator (&): Gets memory address of a variable
- Dereference Operator (*): Accesses value at a memory address
- Null Pointer: Pointer that doesn't point to any valid location
- Memory Address: Unique location in computer's memory
- Pointer Size: Same for all data types (typically 4 or 8 bytes)
1. Basic Pointers
data_type* pointer_name;
// Examples:
int* ptr1; // Pointer to integer
float* ptr2; // Pointer to float
char* ptr3; // Pointer to character
double* ptr4; // Pointer to double
Declaration, Initialization, and Basic Operations
#include <iostream>
using namespace std;
int main() {
// Basic variable
int number = 42;
cout << "Variable 'number' value: " << number << endl;
cout << "Variable 'number' address: " << &number << endl << endl;
// 1. Pointer declaration and initialization
int* ptr = &number; // ptr stores address of number
cout << "1. Pointer Basics:" << endl;
cout << "Pointer 'ptr' value (address it stores): " << ptr << endl;
cout << "Value at address stored in ptr (*ptr): " << *ptr << endl;
cout << "Address of pointer itself (&ptr): " << &ptr << endl << endl;
// 2. Modifying value through pointer
cout << "2. Modifying through pointer:" << endl;
cout << "Before: number = " << number << endl;
*ptr = 100; // Modify value at the address ptr points to
cout << "After *ptr = 100: number = " << number << endl << endl;
// 3. Pointer to different types
cout << "3. Pointers to different data types:" << endl;
float f = 3.14f;
float* fptr = &f;
cout << "Float pointer: f = " << *fptr << ", address = " << fptr << endl;
char c = 'A';
char* cptr = &c;
cout << "Char pointer: c = '" << *cptr << "', address = " << (void*)cptr << endl;
double d = 2.71828;
double* dptr = &d;
cout << "Double pointer: d = " << *dptr << ", address = " << dptr << endl << endl;
// 4. Null pointer
cout << "4. Null Pointers:" << endl;
int* nullPtr = nullptr; // Modern C++ (C++11 and later)
int* nullPtr2 = NULL; // Traditional C/C++
int* nullPtr3 = 0; // Alternative
cout << "nullptr: " << nullPtr << endl;
cout << "NULL: " << nullPtr2 << endl;
cout << "0: " << nullPtr3 << endl;
// Always check before dereferencing
if (nullPtr == nullptr) {
cout << "Pointer is null - safe to check!" << endl;
}
// 5. Pointer size (platform dependent)
cout << "\n5. Pointer Sizes:" << endl;
cout << "Size of int*: " << sizeof(int*) << " bytes" << endl;
cout << "Size of float*: " << sizeof(float*) << " bytes" << endl;
cout << "Size of char*: " << sizeof(char*) << " bytes" << endl;
cout << "Size of double*: " << sizeof(double*) << " bytes" << endl;
cout << "Size of void*: " << sizeof(void*) << " bytes" << endl << endl;
// 6. Pointer reassignment
cout << "6. Pointer Reassignment:" << endl;
int x = 10, y = 20;
int* p = &x;
cout << "Initially p points to x: *p = " << *p << endl;
p = &y; // Now p points to y
cout << "After p = &y: *p = " << *p << endl;
// 7. Const pointers
cout << "\n7. Const with Pointers:" << endl;
// Pointer to const data
const int constant = 100;
const int* ptrToConst = &constant;
cout << "Pointer to const: *ptrToConst = " << *ptrToConst << endl;
// *ptrToConst = 200; // Error: cannot modify const data through pointer
// Const pointer (cannot point elsewhere)
int value = 50;
int* const constPtr = &value;
*constPtr = 60; // OK: can modify the data
cout << "Const pointer: *constPtr = " << *constPtr << endl;
// constPtr = &x; // Error: cannot reassign const pointer
// Const pointer to const data
const int* const constPtrToConst = &constant;
cout << "Const pointer to const: *constPtrToConst = " << *constPtrToConst << endl << endl;
return 0;
}
Best Practices for Basic Pointers:
- Always initialize pointers (use
nullptrif not pointing to valid memory) - Check for null before dereferencing
- Use
constappropriately to prevent accidental modifications - Be aware of pointer sizes on different platforms (32-bit vs 64-bit)
- Understand the difference between pointer value (address) and pointed value
2. Complete Pointer Types Comparison
The following table compares all pointer types in C++ with their syntax, use cases, and characteristics:
| Pointer Type | Syntax | When to Use | Characteristics |
|---|---|---|---|
| Basic Pointer | int* ptr; | Direct memory access, dynamic allocation | Stores address, can be reassigned |
| Pointer to Pointer | int** ptr; | Dynamic 2D arrays, function arguments | Points to another pointer, double indirection |
| Void Pointer (Generic) | void* ptr; | Generic functions, memory operations | Can point to any type, cannot be dereferenced directly |
| Function Pointer | int (*funcPtr)(int, int); | Callbacks, event handlers, strategy pattern | Points to function, enables runtime function selection |
| Array Pointer | int (*arrPtr)[10]; | Pointer to entire array, multi-dimensional arrays | Different from pointer to first element |
| Const Pointer | int* const ptr; | Pointer that cannot be reassigned | Constant pointer, variable data |
| Pointer to Const | const int* ptr; | Read-only access to data | Variable pointer, constant data |
| unique_ptr (C++11) | unique_ptr<int> ptr; | Single ownership, resource management | Automatically deletes, cannot be copied |
| shared_ptr (C++11) | shared_ptr<int> ptr; | Shared ownership, reference counting | Automatically deletes when last reference goes |
| weak_ptr (C++11) | weak_ptr<int> ptr; | Break circular references | Non-owning reference to shared_ptr |
| this Pointer | this | Within class methods to access members | Implicit pointer to current object |
3. Advanced Pointer Types
Pointer to Pointer (Double Pointer)
#include <iostream>
using namespace std;
int main() {
cout << "=== POINTER TO POINTER (DOUBLE POINTER) ===\n\n";
// Single pointer
int value = 42;
int* ptr = &value;
// Double pointer
int** dptr = &ptr;
cout << "1. Basic double pointer operations:" << endl;
cout << "value = " << value << endl;
cout << "&value = " << &value << endl;
cout << "ptr = " << ptr << " (address of value)" << endl;
cout << "*ptr = " << *ptr << " (value at address)" << endl;
cout << "&ptr = " << &ptr << " (address of ptr)" << endl;
cout << "dptr = " << dptr << " (address of ptr)" << endl;
cout << "*dptr = " << *dptr << " (value at dptr = ptr = address of value)" << endl;
cout << "**dptr = " << **dptr << " (value at *dptr = value)" << endl << endl;
// 2. Modifying through double pointer
cout << "2. Modifying through double pointer:" << endl;
**dptr = 100;
cout << "After **dptr = 100:" << endl;
cout << "value = " << value << endl;
cout << "*ptr = " << *ptr << endl;
cout << "**dptr = " << **dptr << endl << endl;
// 3. Dynamic 2D array using double pointers
cout << "3. Dynamic 2D array (matrix):" << endl;
int rows = 3, cols = 4;
// Allocate array of pointers (rows)
int** matrix = new int*[rows];
// Allocate each row
for (int i = 0; i < rows; i++) {
matrix[i] = new int[cols];
}
// Initialize matrix
int counter = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = counter++;
}
}
// Print matrix
cout << "Matrix " << rows << "x" << cols << ":" << endl;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cout << matrix[i][j] << "\t";
}
cout << endl;
}
// Deallocate memory
for (int i = 0; i < rows; i++) {
delete[] matrix[i];
}
delete[] matrix;
// 4. Pointer to pointer in function arguments
cout << "\n4. Pointer to pointer in functions:" << endl;
void allocateMemory(int** ptr, int size);
void printArray(int* arr, int size);
int* dynamicArray = nullptr;
allocateMemory(&dynamicArray, 5); // Pass address of pointer
if (dynamicArray) {
for (int i = 0; i < 5; i++) {
dynamicArray[i] = (i + 1) * 10;
}
cout << "Dynamic array: ";
printArray(dynamicArray, 5);
delete[] dynamicArray;
}
return 0;
}
void allocateMemory(int** ptr, int size) {
*ptr = new int[size]; // Allocate memory and assign to *ptr
cout << "Allocated " << size << " integers at address " << *ptr << endl;
}
void printArray(int* arr, int size) {
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
Void Pointers (Generic Pointers)
#include <iostream>
#include <cstring>
using namespace std;
int main() {
cout << "=== VOID POINTERS (GENERIC POINTERS) ===\n\n";
// 1. Basic void pointer operations
cout << "1. Basic void pointer:" << endl;
int intValue = 42;
float floatValue = 3.14f;
char charValue = 'A';
void* voidPtr;
// Point to integer
voidPtr = &intValue;
cout << "voidPtr points to int: " << *(int*)voidPtr << endl;
// Point to float
voidPtr = &floatValue;
cout << "voidPtr points to float: " << *(float*)voidPtr << endl;
// Point to char
voidPtr = &charValue;
cout << "voidPtr points to char: '" << *(char*)voidPtr << "'" << endl << endl;
// 2. Memory operations with void pointers
cout << "2. Memory operations:" << endl;
// Allocate memory
void* memory = malloc(100); // Allocate 100 bytes
if (memory) {
cout << "Allocated 100 bytes at address: " << memory << endl;
// Use as different types
int* intArray = (int*)memory;
for (int i = 0; i < 5; i++) {
intArray[i] = i * 10;
}
cout << "As int array: ";
for (int i = 0; i < 5; i++) {
cout << intArray[i] << " ";
}
cout << endl;
// Use as char array (string)
char* str = (char*)memory;
strcpy(str, "Hello");
cout << "As string: " << str << endl;
free(memory);
}
// 3. Generic function using void pointer
cout << "\n3. Generic print function:" << endl;
void printValue(void* data, char type) {
switch (type) {
case 'i':
cout << "Integer: " << *(int*)data << endl;
break;
case 'f':
cout << "Float: " << *(float*)data << endl;
break;
case 'c':
cout << "Char: '" << *(char*)data << "'" << endl;
break;
case 'd':
cout << "Double: " << *(double*)data << endl;
break;
default:
cout << "Unknown type" << endl;
}
}
int i = 100;
float f = 2.718f;
char c = 'Z';
double d = 1.618;
printValue(&i, 'i');
printValue(&f, 'f');
printValue(&c, 'c');
printValue(&d, 'd');
// 4. Array of void pointers
cout << "\n4. Array of void pointers (heterogeneous collection):" << endl;
void* mixedArray[4];
mixedArray[0] = &i;
mixedArray[1] = &f;
mixedArray[2] = &c;
mixedArray[3] = &d;
char types[] = {'i', 'f', 'c', 'd'};
for (int j = 0; j < 4; j++) {
printValue(mixedArray[j], types[j]);
}
return 0;
}
Important Notes about Void Pointers:
- Cannot be dereferenced directly (must be cast to appropriate type first)
- No pointer arithmetic allowed on void pointers
- Use with caution - type safety is the programmer's responsibility
- Commonly used in low-level memory operations and generic programming
- Modern C++ alternatives: templates,
std::any(C++17)
4. Function Pointers
#include <iostream>
#include <cmath>
#include <functional> // For std::function (C++11)
using namespace std;
// Function prototypes
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; }
float divide(int a, int b) { return (b != 0) ? (float)a / b : 0; }
// Function that takes function pointer as parameter
int operate(int x, int y, int (*operation)(int, int)) {
return operation(x, y);
}
// Typedef for function pointer (makes syntax cleaner)
typedef int (*MathOperation)(int, int);
// Another way: using alias (C++11)
using MathFunc = int (*)(int, int);
int main() {
cout << "=== FUNCTION POINTERS ===\n\n";
// 1. Basic function pointer declaration and use
cout << "1. Basic function pointers:" << endl;
int (*funcPtr)(int, int); // Declaration
funcPtr = add; // Point to add function
cout << "add(10, 5) = " << funcPtr(10, 5) << endl;
funcPtr = subtract; // Point to subtract function
cout << "subtract(10, 5) = " << funcPtr(10, 5) << endl;
funcPtr = multiply; // Point to multiply function
cout << "multiply(10, 5) = " << funcPtr(10, 5) << endl << endl;
// 2. Array of function pointers
cout << "2. Array of function pointers:" << endl;
MathOperation operations[] = {add, subtract, multiply};
const char* operationNames[] = {"Addition", "Subtraction", "Multiplication"};
for (int i = 0; i < 3; i++) {
cout << operationNames[i] << ": "
<< operations[i](10, 5) << endl;
}
cout << endl;
// 3. Function pointer as parameter
cout << "3. Function pointer as function parameter:" << endl;
cout << "operate(10, 5, add) = " << operate(10, 5, add) << endl;
cout << "operate(10, 5, subtract) = " << operate(10, 5, subtract) << endl;
cout << "operate(10, 5, multiply) = " << operate(10, 5, multiply) << endl << endl;
// 4. Callback function example
cout << "4. Callback function example:" << endl;
void processArray(int arr[], int size, int (*callback)(int)) {
for (int i = 0; i < size; i++) {
arr[i] = callback(arr[i]);
}
}
int square(int x) { return x * x; }
int cube(int x) { return x * x * x; }
int doubleValue(int x) { return x * 2; }
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);
cout << "Original array: ";
for (int i = 0; i < size; i++) cout << numbers[i] << " ";
cout << endl;
processArray(numbers, size, square);
cout << "After squaring: ";
for (int i = 0; i < size; i++) cout << numbers[i] << " ";
cout << endl;
// Reset array
for (int i = 0; i < size; i++) numbers[i] = i + 1;
processArray(numbers, size, cube);
cout << "After cubing: ";
for (int i = 0; i < size; i++) cout << numbers[i] << " ";
cout << endl << endl;
// 5. Comparison with std::function (C++11)
cout << "5. std::function (C++11 alternative):" << endl;
// std::function is more flexible
std::function func;
func = add;
cout << "std::function add: " << func(10, 5) << endl;
func = subtract;
cout << "std::function subtract: " << func(10, 5) << endl;
// Can also use lambda functions
func = [](int a, int b) { return a * b; };
cout << "std::function lambda: " << func(10, 5) << endl << endl;
// 6. Member function pointers
cout << "6. Member function pointers:" << endl;
class Calculator {
public:
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
static int staticAdd(int a, int b) { return a + b; }
};
Calculator calc;
// Non-static member function pointer
int (Calculator::*memberFuncPtr)(int, int) = &Calculator::add;
cout << "Member function add: " << (calc.*memberFuncPtr)(10, 5) << endl;
// Static member function pointer (same as regular function pointer)
int (*staticFuncPtr)(int, int) = &Calculator::staticAdd;
cout << "Static member function add: " << staticFuncPtr(10, 5) << endl;
return 0;
}
Function Pointer Applications:
- Callbacks: Event handlers, GUI programming
- Strategy Pattern: Select algorithm at runtime
- Sorting: Custom comparison functions
- Plugin Architecture: Dynamic function loading
- State Machines: Different behaviors for states
5. Smart Pointers (C++11+)
#include <memory>
// unique_ptr (exclusive ownership):
std::unique_ptr<Type> ptr = std::make_unique<Type>(args);
// shared_ptr (shared ownership):
std::shared_ptr<Type> ptr = std::make_shared<Type>(args);
// weak_ptr (non-owning reference):
std::weak_ptr<Type> wptr = sharedPtr;
#include <iostream>
#include <memory> // For smart pointers
#include <vector>
using namespace std;
class Resource {
private:
string name;
public:
Resource(string n) : name(n) {
cout << "Resource '" << name << "' created" << endl;
}
~Resource() {
cout << "Resource '" << name << "' destroyed" << endl;
}
void use() {
cout << "Using resource '" << name << "'" << endl;
}
string getName() const { return name; }
};
int main() {
cout << "=== SMART POINTERS (C++11 and later) ===\n\n";
// 1. unique_ptr (exclusive ownership)
cout << "1. unique_ptr (Exclusive Ownership):" << endl;
cout << "-------------------------------------" << endl;
{
// Create unique_ptr using make_unique (C++14)
unique_ptr res1 = make_unique("Unique Resource 1");
res1->use();
// unique_ptr cannot be copied
// unique_ptr res2 = res1; // Error: copy constructor deleted
// But can be moved
unique_ptr res2 = move(res1);
if (!res1) {
cout << "res1 is now empty after move" << endl;
}
if (res2) {
cout << "res2 now owns: " << res2->getName() << endl;
}
// Array support
unique_ptr arr = make_unique(3);
cout << "Created array of 3 resources" << endl;
// Custom deleter
auto customDeleter = [](Resource* r) {
cout << "Custom deleter called for: " << r->getName() << endl;
delete r;
};
unique_ptr res3(new Resource("Custom Deleted"), customDeleter);
} // All unique_ptrs automatically destroyed here
cout << endl;
// 2. shared_ptr (shared ownership)
cout << "2. shared_ptr (Shared Ownership):" << endl;
cout << "----------------------------------" << endl;
{
// Create shared_ptr using make_shared (more efficient)
shared_ptr shared1 = make_shared("Shared Resource 1");
cout << "Use count: " << shared1.use_count() << endl;
// Create another shared_ptr pointing to same resource
shared_ptr shared2 = shared1;
cout << "After sharing, use count: " << shared1.use_count() << endl;
shared1->use();
shared2->use();
// Create more references
{
shared_ptr shared3 = shared2;
cout << "In inner scope, use count: " << shared1.use_count() << endl;
} // shared3 destroyed
cout << "After inner scope, use count: " << shared1.use_count() << endl;
// Reset shared_ptr
shared1.reset();
cout << "After reset shared1, use count: " << shared2.use_count() << endl;
if (!shared1) {
cout << "shared1 is now empty" << endl;
}
} // Last shared_ptr destroyed, resource freed
cout << endl;
// 3. weak_ptr (non-owning reference)
cout << "3. weak_ptr (Non-owning Reference):" << endl;
cout << "------------------------------------" << endl;
{
shared_ptr shared = make_shared("Resource for weak_ptr");
weak_ptr weak = shared;
cout << "shared use count: " << shared.use_count() << endl;
// Convert weak_ptr to shared_ptr to access resource
if (auto locked = weak.lock()) {
cout << "Resource accessed via weak_ptr: " << locked->getName() << endl;
cout << "Now use count: " << shared.use_count() << endl;
}
// Reset shared_ptr
shared.reset();
cout << "After reset shared, use count: " << shared.use_count() << endl;
// Check if weak_ptr is expired
if (weak.expired()) {
cout << "weak_ptr is expired (resource destroyed)" << endl;
}
// Trying to lock expired weak_ptr
auto locked = weak.lock();
if (!locked) {
cout << "Failed to lock expired weak_ptr" << endl;
}
}
cout << endl;
// 4. Circular reference problem and solution with weak_ptr
cout << "4. Circular Reference Problem:" << endl;
cout << "--------------------------------" << endl;
class Node {
public:
string name;
shared_ptr partner; // Problem: circular reference
Node(string n) : name(n) {
cout << "Node '" << name << "' created" << endl;
}
~Node() {
cout << "Node '" << name << "' destroyed" << endl;
}
};
class SafeNode {
public:
string name;
weak_ptr partner; // Solution: use weak_ptr
SafeNode(string n) : name(n) {
cout << "SafeNode '" << name << "' created" << endl;
}
~SafeNode() {
cout << "SafeNode '" << name << "' destroyed" << endl;
}
};
cout << "\nProblem (circular reference with shared_ptr):" << endl;
{
auto nodeA = make_shared("A");
auto nodeB = make_shared("B");
nodeA->partner = nodeB;
nodeB->partner = nodeA;
cout << "Use count A: " << nodeA.use_count() << endl;
cout << "Use count B: " << nodeB.use_count() << endl;
// Memory leak! Nodes won't be destroyed automatically
}
cout << "\nSolution (using weak_ptr to break circular reference):" << endl;
{
auto nodeA = make_shared("A");
auto nodeB = make_shared("B");
nodeA->partner = nodeB;
nodeB->partner = nodeA;
cout << "Use count A: " << nodeA.use_count() << endl;
cout << "Use count B: " << nodeB.use_count() << endl;
// No memory leak! Nodes will be destroyed properly
}
// 5. Practical example: Resource manager
cout << "\n5. Practical Example: Resource Manager" << endl;
cout << "----------------------------------------" << endl;
class Texture {
private:
string filename;
public:
Texture(string fname) : filename(fname) {
cout << "Loading texture: " << filename << endl;
}
~Texture() {
cout << "Unloading texture: " << filename << endl;
}
void render() {
cout << "Rendering texture: " << filename << endl;
}
};
class TextureManager {
private:
vector> textures;
public:
shared_ptr loadTexture(string filename) {
// Check if already loaded
for (auto& tex : textures) {
// In real implementation, compare filenames
}
// Load new texture
auto texture = make_shared(filename);
textures.push_back(texture);
return texture;
}
void listTextures() {
cout << "Loaded textures: " << textures.size() << endl;
}
};
TextureManager manager;
auto tex1 = manager.loadTexture("wall.png");
auto tex2 = manager.loadTexture("floor.png");
auto tex3 = manager.loadTexture("ceiling.png");
// Multiple objects can share same texture
auto tex4 = tex1; // Share wall texture
tex1->render();
tex2->render();
manager.listTextures();
return 0;
}
unique_ptr
- Ownership: Exclusive
- Copyable: No
- Movable: Yes
- Use when: Single owner needed
- Overhead: Minimal
shared_ptr
- Ownership: Shared
- Copyable: Yes
- Movable: Yes
- Use when: Multiple owners needed
- Overhead: Reference counting
weak_ptr
- Ownership: None
- Copyable: Yes
- Movable: Yes
- Use when: Break circular references
- Overhead: Minimal
6. Pointer Arithmetic
#include <iostream>
using namespace std;
int main() {
cout << "=== POINTER ARITHMETIC ===\n\n";
// 1. Basic pointer arithmetic
cout << "1. Basic Pointer Arithmetic:" << endl;
cout << "------------------------------" << endl;
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr; // Points to first element
cout << "Array elements: ";
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
cout << endl << endl;
cout << "Pointer initially points to: arr[0] = " << *ptr << endl;
cout << "Pointer address: " << ptr << endl;
// Increment pointer (moves to next element)
ptr++;
cout << "\nAfter ptr++:" << endl;
cout << "Points to: arr[1] = " << *ptr << endl;
cout << "Pointer address: " << ptr << endl;
cout << "Address difference: " << (ptr - arr) * sizeof(int) << " bytes" << endl;
// Decrement pointer
ptr--;
cout << "\nAfter ptr-- (back to start):" << endl;
cout << "Points to: arr[0] = " << *ptr << endl;
// Add integer to pointer
ptr = ptr + 3;
cout << "\nAfter ptr = ptr + 3:" << endl;
cout << "Points to: arr[3] = " << *ptr << endl;
// Subtract integer from pointer
ptr = ptr - 2;
cout << "\nAfter ptr = ptr - 2:" << endl;
cout << "Points to: arr[1] = " << *ptr << endl;
// Pointer difference
int* ptr2 = &arr[4];
cout << "\nPointer difference (ptr2 - ptr):" << endl;
cout << "ptr points to arr[1] = " << *ptr << endl;
cout << "ptr2 points to arr[4] = " << *ptr2 << endl;
cout << "ptr2 - ptr = " << (ptr2 - ptr) << " elements" << endl;
cout << "In bytes: " << (ptr2 - ptr) * sizeof(int) << " bytes" << endl << endl;
// 2. Array traversal using pointer arithmetic
cout << "2. Array Traversal with Pointers:" << endl;
cout << "----------------------------------" << endl;
double numbers[] = {1.1, 2.2, 3.3, 4.4, 5.5};
double* numPtr = numbers;
int numCount = sizeof(numbers) / sizeof(numbers[0]);
cout << "Array traversal:" << endl;
for (int i = 0; i < numCount; i++) {
cout << "numbers[" << i << "] = " << *(numPtr + i)
<< " at address " << (numPtr + i) << endl;
}
cout << endl;
// 3. Pointer arithmetic with different data types
cout << "3. Different Data Types:" << endl;
cout << "-------------------------" << endl;
char chars[] = {'A', 'B', 'C', 'D', 'E'};
char* charPtr = chars;
cout << "Char array (1 byte each):" << endl;
cout << "charPtr = " << (void*)charPtr << endl;
cout << "charPtr + 1 = " << (void*)(charPtr + 1) << endl;
cout << "Difference: " << (charPtr + 1) - charPtr << " byte" << endl << endl;
int ints[] = {1, 2, 3, 4, 5};
int* intPtr = ints;
cout << "Int array (typically 4 bytes each):" << endl;
cout << "intPtr = " << intPtr << endl;
cout << "intPtr + 1 = " << intPtr + 1 << endl;
cout << "Difference: " << (intPtr + 1) - intPtr << " elements" << endl;
cout << "Byte difference: " << (char*)(intPtr + 1) - (char*)intPtr << " bytes" << endl << endl;
// 4. Pointer arithmetic in strings
cout << "4. String Manipulation:" << endl;
cout << "------------------------" << endl;
char str[] = "Hello, World!";
char* strPtr = str;
cout << "Original string: " << str << endl;
// Find length using pointers
char* endPtr = strPtr;
while (*endPtr != '\0') {
endPtr++;
}
cout << "String length (pointer arithmetic): " << (endPtr - strPtr) << endl;
// Reverse string using pointers
char* start = str;
char* end = str;
while (*end) end++; // Move to null terminator
end--; // Move back to last character
cout << "Reversed string: ";
while (end >= start) {
cout << *end;
end--;
}
cout << endl << endl;
// 5. Pointer arithmetic with 2D arrays
cout << "5. 2D Arrays and Pointers:" << endl;
cout << "---------------------------" << endl;
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// Pointer to first element of 2D array
int* flatPtr = &matrix[0][0];
cout << "2D array as flat memory:" << endl;
for (int i = 0; i < 3 * 4; i++) {
cout << *(flatPtr + i) << " ";
if ((i + 1) % 4 == 0) cout << endl;
}
cout << endl;
// 6. Pointer comparison
cout << "6. Pointer Comparison:" << endl;
cout << "----------------------" << endl;
int values[] = {10, 20, 30, 40, 50};
int* p1 = &values[1];
int* p2 = &values[3];
cout << "p1 points to: " << *p1 << " at " << p1 << endl;
cout << "p2 points to: " << *p2 << " at " << p2 << endl;
if (p1 < p2) {
cout << "p1 comes before p2 in memory" << endl;
}
if (p1 != p2) {
cout << "p1 and p2 point to different locations" << endl;
}
// 7. Bounds checking with pointer arithmetic
cout << "\n7. Bounds Checking:" << endl;
cout << "-------------------" << endl;
int buffer[10] = {0};
int* bufPtr = buffer;
int* bufEnd = buffer + 10; // One past the end
// Safe iteration
for (int* p = buffer; p < bufEnd; p++) {
*p = (p - buffer) * 10; // Calculate index using pointer arithmetic
}
cout << "Buffer after filling: ";
for (int i = 0; i < 10; i++) {
cout << buffer[i] << " ";
}
cout << endl;
return 0;
}
Pointer Arithmetic Rules:
- Adding
nto a pointer moves it forward byn * sizeof(type)bytes - Subtracting
nfrom a pointer moves it backward byn * sizeof(type)bytes - Pointer subtraction gives the number of elements between them
- Pointer arithmetic only works within the same array or allocated memory block
- Comparing pointers is only valid when they point to the same array
7. Best Practices & Common Pitfalls
| Common Error | Example | Solution |
|---|---|---|
| Dereferencing Null Pointer | int* ptr = nullptr; *ptr = 5; |
Always check for null before dereferencing |
| Dangling Pointer | int* ptr = new int; delete ptr; *ptr = 10; |
Set pointer to null after deletion, use smart pointers |
| Memory Leak | int* ptr = new int[100]; // No delete |
Always match new with delete, use RAII/smart pointers |
| Buffer Overflow | int arr[5]; int* p = arr + 10; |
Check bounds, use containers like vector |
| Type Mismatch | float* ptr = (float*)&intVar; |
Avoid unsafe casts, use proper type conversions |
| Pointer Arithmetic on Wrong Type | void* ptr; ptr++; |
Don't use arithmetic on void pointers |
| Returning Local Variable Address | int* func() { int x; return &x; } |
Don't return address of local variables |
| Double Free | delete ptr; delete ptr; |
Set pointer to null after deletion |
Best Practices
- Always initialize pointers (use
nullptrfor empty) - Prefer smart pointers over raw pointers for ownership
- Use
constwhenever possible - Check pointer validity before dereferencing
- Match every
newwith exactly onedelete - Use range-based for loops or iterators when possible
- Document pointer ownership responsibilities
Performance Tips
- Minimize pointer indirection (deep nesting hurts cache)
- Use local pointers for frequently accessed data
- Avoid unnecessary pointer arithmetic in tight loops
- Consider reference types when ownership isn't needed
- Profile before optimizing pointer usage
Modern C++ Pointer Guidelines
In modern C++ (C++11 and later), prefer smart pointers (unique_ptr, shared_ptr) for ownership, use raw pointers only for non-owning references, and leverage references when null isn't needed. Use std::array or std::vector instead of raw arrays.
Quick Quiz: Test Your Knowledge
What will be the output of this code?
int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr + 2;
cout << *(ptr - 1) + *(ptr + 1);
Summary & Key Takeaways
Pointer Fundamentals
- Pointers store memory addresses
- Use
&to get address,*to dereference - Always initialize pointers
- Check for null before dereferencing
- Understand pointer size and platform differences
Smart Pointers (Modern C++)
unique_ptr: Exclusive ownership, cannot be copiedshared_ptr: Shared ownership with reference countingweak_ptr: Non-owning reference to break circular dependencies- Prefer smart pointers for automatic memory management
- Use
make_uniqueandmake_sharedfor creation
Advanced Pointer Types
- Pointer to pointer: For multi-dimensional dynamic arrays
- Void pointer: Generic pointers, requires explicit casting
- Function pointer: Enables callbacks and runtime polymorphism
thispointer: Implicit pointer to current object- Const pointers: Various combinations for safety
Pointer Arithmetic
- Adding
nmoves pointer byn * sizeof(type) - Use for array traversal and string manipulation
- Pointer subtraction gives element count difference
- Only valid within same allocated memory block
- Essential for low-level memory operations
When to Use Different Pointer Types
- Raw pointers: Non-owning references, legacy code, low-level operations
- Smart pointers: Memory ownership, automatic cleanup, modern C++ code
- References: When null isn't needed, function parameters, return values
- Function pointers: Callbacks, strategy pattern, event systems
- Avoid: Raw pointers for ownership, manual memory management in modern code
Next Steps
Mastering pointers is crucial for advanced C++ programming. Practice with pointer-based data structures (linked lists, trees), explore memory management patterns, learn about move semantics (C++11), and study the Standard Template Library (STL) containers that internally use pointers. Understanding pointers deeply will make you a more effective C++ programmer.