Multi-Dimensional Arrays in C++

A multi-dimensional array is an array whose elements are themselves arrays. In C++, a 2D array int a[R][C] is stored in row-major order — all elements of row 0 come first, then row 1, etc.

Memory Layout — Row-Major Order

int arr[3][4] — Memory Layout (Row-Major) 2D View: row 0 1 [0][0] 2 [0][1] 3 [0][2] 4 [0][3] row 1 5 [1][0] 6 [1][1] 7 [1][2] ★ 8 [1][3] row 2 9 [2][0] 10 [2][1] 11 [2][2] 12 [2][3] Linear Memory (contiguous row-major): ← low address high address → 1 2 3 4 5 6 7 8 9 10 11 12 base+0 base+6*4=[1][2] Address Formula: &arr[i][j] = base + (i × C + j) × sizeof(int) &arr[1][2] = base + (1×4 + 2) × 4 = base + 24 (& stored at index 6) 3D array: int cube[L][R][C] Address: base + (l×R×C + r×C + c) × sizeof(int) Stored as: [layer0, row0,cols] [layer0, row1,cols] ... [layer1, row0,cols] ...

Static & Dynamic 2D Arrays

#include <iostream> using namespace std; // ── Static 2D array ────────────────────────────────────────────────── void staticExample() { const int R = 3, C = 4; int arr[R][C] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; // Access arr[1][2]: row 1, column 2 cout << arr[1][2] << "\n"; // 7 cout << "Address of arr[1][2]: " << &arr[1][2] << "\n"; // Pointer arithmetic: arr+6 points to arr[1][2] cout << *(*(arr+1)+2) << "\n"; // 7, using pointer arithmetic } // ── Dynamic 2D array (pointer-to-pointer) ──────────────────────────── int** alloc2D(int rows, int cols) { int** m = new int*[rows]; for (int i = 0; i < rows; i++) m[i] = new int[cols](); // zero-initialised return m; } void free2D(int** m, int rows) { for (int i = 0; i < rows; i++) delete[] m[i]; delete[] m; } // ── Flat 1D as 2D (better cache performance) ───────────────────────── void flat2D(int rows, int cols) { int* flat = new int[rows * cols](); // Access flat[r][c] as flat[r*cols + c] flat[1*cols + 2] = 99; // set [1][2] = 99 cout << flat[1*cols + 2] << "\n"; // 99 delete[] flat; } // ── Matrix multiplication (2D arrays) ───────────────────────────────── void matMul(int** A, int** B, int** C, int N) { for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) { C[i][j] = 0; for (int k = 0; k < N; k++) C[i][j] += A[i][k] * B[k][j]; // O(N³) } }

3D Arrays — Real Example: Video Frame Buffer

// 3D array: frames × rows × cols (e.g. grayscale video) const int FRAMES = 10, ROWS = 1080, COLS = 1920; // Static (large; usually heap-allocated): // uint8_t video[FRAMES][ROWS][COLS]; // Dynamic 3D allocation uint8_t*** alloc3D(int f, int r, int c) { uint8_t*** v = new uint8_t**[f]; for (int i = 0; i < f; i++) { v[i] = new uint8_t*[r]; for (int j = 0; j < r; j++) v[i][j] = new uint8_t[c](); } return v; } // Access: video[frame][row][col]

Arrays of Structures

Structures let you bundle related data. Arrays of structures create collections of records — ideal for databases, game entities, and scientific data.

Memory Layout: AoS vs SoA

Array of Structures (AoS) Memory Layout — Student[3] students[0] id=101 name="Alice" gpa=3.8 contiguous in memory → cache-friendly for accessing all fields of one student students[1] id=102 name="Bob" gpa=3.5 students[2] id=103 name="Charlie" gpa=3.9 SoA (Structure of Arrays) — Alternative for SIMD/vectorisation: ids[] 101, 102, 103 names[] "Alice", "Bob", "Charlie" gpas[] 3.8, 3.5, 3.9 AoS = faster when accessing all fields of one record. SoA = faster when accessing one field of all records (SIMD-friendly).

Complete C++ Example — Student Database

#include <iostream> #include <string> #include <algorithm> using namespace std; struct Student { int id; string name; float gpa; int age; }; // Sort comparators bool byGpaDesc(const Student& a, const Student& b) { return a.gpa > b.gpa; } void printStudents(const Student* s, int n) { for (int i = 0; i < n; i++) cout << "ID:" << s[i].id << " Name:" << s[i].name << " GPA:" << s[i].gpa << " Age:" << s[i].age << "\n"; } int main() { // Array of structures on the stack Student students[5] = { {101, "Alice", 3.8f, 20}, {102, "Bob", 3.5f, 21}, {103, "Charlie", 3.9f, 19}, {104, "Diana", 3.7f, 20}, {105, "Eve", 3.6f, 22} }; // Sort by GPA descending sort(students, students+5, byGpaDesc); cout << "Top students by GPA:\n"; printStudents(students, 5); // Dynamic array of structs (heap) int n; cout << "\nEnter number of students: "; cin >> n; Student* db = new Student[n]; for (int i = 0; i < n; i++) { db[i].id = 200 + i; cin >> db[i].name >> db[i].gpa >> db[i].age; } // Pointer arithmetic to access struct members for (int i = 0; i < n; i++) { Student* p = db + i; // same as &db[i] cout << p->name << " GPA=" << p->gpa << "\n"; } delete[] db; return 0; }

Struct with Pointers — Linked Node

// Self-referential struct: each node holds a pointer to next struct Node { int data; Node* next; // pointer to same type! Node(int d) : data(d), next(nullptr) {} };

Stack Implementation with Pointers

A stack is a LIFO (Last-In, First-Out) data structure. We implement it using a linked list of nodes connected by pointers — no resizing needed, O(1) push and pop.

Visual: Push and Pop

Stack: Linked-List Implementation (LIFO) State: push(10)→push(20)→push(30) top* data=30 ← TOP next→ data=20 next→ data=10 next→ null After pop(): Remove node 30, return 30. top now points to node 20. top* data=20 ← new TOP next→ data=10 next→ null data=30 → deleted/freed Stack Operations push(x): Create node, node→next = top, top = node O(1) pop(): Save top→data, temp = top, top = top→next, delete temp O(1) peek(): Return top→data (without removing) O(1)

Interactive Stack Simulation

Stack (top → bottom): empty

Full C++ Stack Implementation

#include <iostream> #include <stdexcept> using namespace std; template<typename T> class Stack { private: struct Node { T data; Node* next; Node(T d, Node* n = nullptr) : data(d), next(n) {} }; Node* topNode = nullptr; int sz = 0; public: // O(1) push: allocate node, link to current top void push(T val) { topNode = new Node(val, topNode); sz++; } // O(1) pop: unlink top node, free memory T pop() { if (!topNode) throw runtime_error("Stack underflow"); T val = topNode->data; Node* tmp = topNode; topNode = topNode->next; delete tmp; sz--; return val; } T peek() const { if (!topNode) throw runtime_error("Empty"); return topNode->data; } bool empty() const { return topNode == nullptr; } int size() const { return sz; } ~Stack() { while (!empty()) pop(); } }; int main() { Stack<int> s; s.push(10); s.push(20); s.push(30); cout << "Top: " << s.peek() << "\n"; // 30 while (!s.empty()) cout << s.pop() << " "; // 30 20 10 cout << "\n"; return 0; }

Queue Implementation with Pointers

A queue is a FIFO (First-In, First-Out) data structure. We maintain both a front (dequeue side) and rear (enqueue side) pointer for O(1) operations.

Visual: Enqueue and Dequeue

Queue: Linked-List Implementation (FIFO) After enqueue(10), enqueue(20), enqueue(30) front* data=10 ← dequeue here next→ data=20 next→ data=30 ← enqueue here next→ null rear* After dequeue(): Remove front (10), return 10. front now points to node 20. front* data=20 ← new front next→ data=30 next→ null enqueue(x): Create node, rear→next = node, rear = node O(1) dequeue(): Save front→data, front = front→next, free old O(1) front(): Return front→data O(1) Special: if front==null → empty; if rear==null → empty too

Interactive Queue Simulation

Queue (front → rear): empty

Full C++ Queue Implementation

#include <iostream> #include <stdexcept> using namespace std; template<typename T> class Queue { private: struct Node { T data; Node* next; Node(T d) : data(d), next(nullptr) {} }; Node* frontNode = nullptr; Node* rearNode = nullptr; int sz = 0; public: // O(1) enqueue: attach to rear void enqueue(T val) { Node* node = new Node(val); if (!rearNode) { // empty queue frontNode = rearNode = node; } else { rearNode->next = node; // link at end rearNode = node; // advance rear } sz++; } // O(1) dequeue: remove from front T dequeue() { if (!frontNode) throw runtime_error("Queue empty"); T val = frontNode->data; Node* tmp = frontNode; frontNode = frontNode->next; if (!frontNode) rearNode = nullptr; // queue became empty delete tmp; sz--; return val; } T front() const { if (!frontNode) throw runtime_error("Empty"); return frontNode->data; } bool empty() const { return frontNode == nullptr; } int size() const { return sz; } ~Queue() { while (!empty()) dequeue(); } }; int main() { Queue<string> q; q.enqueue("first"); q.enqueue("second"); q.enqueue("third"); cout << "Front: " << q.front() << "\n"; // first while (!q.empty()) cout << q.dequeue() << " "; // first second third cout << "\n"; return 0; }

Comparison: Stack vs Queue

FeatureStack (LIFO)Queue (FIFO)
OrderLast in, first outFirst in, first out
Operationspush, pop, peekenqueue, dequeue, front
Pointer neededtop onlyfront + rear
Use casesDFS, function calls, undoBFS, print queue, task scheduling
Complexity (push/pop)O(1)O(1)