Stacks and queue are foundational linear data structures governed by LIFO and FIFO principles, respectively. This article demonstrates how to implement both using static arrays in C, C#, and C++—with redesigned logic, renamed identifiers, and structural variations while preserving correctness and clarity.
Array-Based Stack Implementation
A stack maintains a single index (head) trakcing the next available slot. All operations run in O(1) time.
Core Operations
- Iniitalize: Set
headto 0 (empty state). - Push: Store value at
data[head], then incrementhead. - Pop: Decrement
head, then returndata[head]. - Peek: Return
data[head - 1]if non-empty. - IsEmpty:
head == 0. - IsFull:
head == capacity.
C Version
#include <stdio.h>
#include <stdbool.h>
#define STACK_CAPACITY 100
typedef struct {
int elements[STACK_CAPACITY];
size_t head;
} ArrayStack;
void stack_init(ArrayStack* s) { s->head = 0; }
bool stack_is_empty(const ArrayStack* s) { return s->head == 0; }
bool stack_is_full(const ArrayStack* s) { return s->head == STACK_CAPACITY; }
void stack_push(ArrayStack* s, int item) {
if (stack_is_full(s)) {
fprintf(stderr, "Error: stack overflow\n");
return;
}
s->elements[s->head++] = item;
}
int stack_pop(ArrayStack* s) {
if (stack_is_empty(s)) {
fprintf(stderr, "Error: stack underflow\n");
return -1;
}
return s->elements[--s->head];
}
int stack_peek(const ArrayStack* s) {
if (stack_is_empty(s)) {
fprintf(stderr, "Error: cannot peek empty stack\n");
return -1;
}
return s->elements[s->head - 1];
}
int main() {
ArrayStack st;
stack_init(&st);
stack_push(&st, 42);
stack_push(&st, 99);
printf("Top: %d\n", stack_peek(&st));
printf("Popped: %d\n", stack_pop(&st));
return 0;
}
C# Version
using System;
class ArrayStack {
private readonly int[] _storage;
private int _size;
public ArrayStack(int capacity) {
_storage = new int[capacity];
_size = 0;
}
public bool IsEmpty => _size == 0;
public bool IsFull => _size == _storage.Length;
public void Push(int value) {
if (IsFull) throw new InvalidOperationException("Stack overflow");
_storage[_size++] = value;
}
public int Pop() {
if (IsEmpty) throw new InvalidOperationException("Stack underflow");
return _storage[--_size];
}
public int Peek() {
if (IsEmpty) throw new InvalidOperationException("Cannot peek empty stack");
return _storage[_size - 1];
}
}
class Program {
static void Main() {
var stack = new ArrayStack(100);
stack.Push(42);
stack.Push(99);
Console.WriteLine($"Top: {stack.Peek()}");
Console.WriteLine($"Popped: {stack.Pop()}");
}
}
C++ Version
#include <iostream>
#include <stdexcept>
#include <vector>
template<typename T>
class ArrayStack {
private:
std::vector<T> buffer;
size_t count;
public:
explicit ArrayStack(size_t cap) : buffer(cap), count(0) {}
bool empty() const { return count == 0; }
bool full() const { return count == buffer.size(); }
void push(const T& val) {
if (full()) throw std::overflow_error("stack overflow");
buffer[count++] = val;
}
T pop() {
if (empty()) throw std::underflow_error("stack underflow");
return buffer[--count];
}
const T& top() const {
if (empty()) throw std::runtime_error("cannot access top of empty stack");
return buffer[count - 1];
}
};
int main() {
ArrayStack<int> s(100);
s.push(42);
s.push(99);
std::cout << "Top: " << s.top() << '\n';
std::cout << "Popped: " << s.pop() << '\n';
return 0;
}
Array-Based Circular Queue Implementation
A circular queue uses modulo arithmetic to reuse array space efficiently. It tracks start (oldest element) and end (next insertion point). Capacity is one less than array size to distinguish full/empty states.
Core Operations
- Initialize: Set
start = end = 0. - Enqueue: Assign to
data[end], updateend = (end + 1) % capacity. - Dequeue: Read from
data[start], updatestart = (start + 1) % capacity. - Front: Return
data[start]if non-empty. - IsEmpty:
start == end. - IsFull:
(end + 1) % capacity == start.
C Version
#include <stdio.h>
#include <stdbool.h>
#define QUEUE_CAPACITY 101 // actual usable slots = 100
typedef struct {
int items[QUEUE_CAPACITY];
size_t begin;
size_t finish;
} CircularQueue;
void queue_init(CircularQueue* q) {
q->begin = q->finish = 0;
}
bool queue_is_empty(const CircularQueue* q) { return q->begin == q->finish; }
bool queue_is_full(const CircularQueue* q) {
return (q->finish + 1) % QUEUE_CAPACITY == q->begin;
}
void queue_enqueue(CircularQueue* q, int item) {
if (queue_is_full(q)) {
fprintf(stderr, "Error: queue overflow\n");
return;
}
q->items[q->finish] = item;
q->finish = (q->finish + 1) % QUEUE_CAPACITY;
}
int queue_dequeue(CircularQueue* q) {
if (queue_is_empty(q)) {
fprintf(stderr, "Error: queue underflow\n");
return -1;
}
int val = q->items[q->begin];
q->begin = (q->begin + 1) % QUEUE_CAPACITY;
return val;
}
int queue_front(const CircularQueue* q) {
if (queue_is_empty(q)) {
fprintf(stderr, "Error: cannot read front of empty queue\n");
return -1;
}
return q->items[q->begin];
}
int main() {
CircularQueue q;
queue_init(&q);
queue_enqueue(&q, 100);
queue_enqueue(&q, 200);
printf("Front: %d\n", queue_front(&q));
printf("Dequeued: %d\n", queue_dequeue(&q));
return 0;
}
C# Version
using System;
class CircularQueue {
private readonly int[] _buffer;
private int _head;
private int _tail;
public CircularQueue(int capacity) {
_buffer = new int[capacity + 1]; // +1 for sentinel
_head = _tail = 0;
}
public bool IsEmpty => _head == _tail;
public bool IsFull => (_tail + 1) % _buffer.Length == _head;
public void Enqueue(int value) {
if (IsFull) throw new InvalidOperationException("Queue overflow");
_buffer[_tail] = value;
_tail = (_tail + 1) % _buffer.Length;
}
public int Dequeue() {
if (IsEmpty) throw new InvalidOperationException("Queue underflow");
int val = _buffer[_head];
_head = (_head + 1) % _buffer.Length;
return val;
}
public int Front() {
if (IsEmpty) throw new InvalidOperationException("Cannot read front of empty queue");
return _buffer[_head];
}
}
class Program {
static void Main() {
var q = new CircularQueue(100);
q.Enqueue(100);
q.Enqueue(200);
Console.WriteLine($"Front: {q.Front()}");
Console.WriteLine($"Dequeued: {q.Dequeue()}");
}
}
C++ Version
#include <iostream>
#include <stdexcept>
#include <vector>
template<typename T>
class CircularQueue {
private:
std::vector<T> storage;
size_t head;
size_t tail;
size_t capacity() const { return storage.size(); }
public:
explicit CircularQueue(size_t cap) : storage(cap + 1), head(0), tail(0) {}
bool empty() const { return head == tail; }
bool full() const { return (tail + 1) % capacity() == head; }
void enqueue(const T& val) {
if (full()) throw std::overflow_error("queue overflow");
storage[tail] = val;
tail = (tail + 1) % capacity();
}
T dequeue() {
if (empty()) throw std::underflow_error("queue underflow");
T val = storage[head];
head = (head + 1) % capacity();
return val;
}
const T& front() const {
if (empty()) throw std::runtime_error("cannot access front of empty queue");
return storage[head];
}
};
int main() {
CircularQueue<int> q(100);
q.enqueue(100);
q.enqueue(200);
std::cout << "Front: " << q.front() << '\n';
std::cout << "Dequeued: " << q.dequeue() << '\n';
return 0;
}