
10. Initializer Lists and Dynamic Data Structures
Using std::initializer_list
std::initializer_list
allows you to initialize an object with a list of values in a convenient way. This often appears in constructors where multiple arguments need to be passed without manually specifying individual parameters.
Key Points:
- It provides a lightweight array-like structure that can be iterated with range-based for loops.
- You cannot directly access elements by index (e.g.,
list[0]
is not allowed). Instead, use iterators or range-based loops.
- Commonly used to simplify initialization.
Simple Example
#include <iostream>
#include <initializer_list>
using namespace std;
class Example {
public:
Example(initializer_list<int> list) {
cout << "Initializing with the following values:" << endl;
for (auto val : list) {
cout << val << " ";
}
cout << endl;
}
};
int main() {
// This calls Example(initializer_list<int>)
Example ex{1, 2, 3, 4, 5};
return 0;
}
Custom Fixed Array Example
Here’s a class FixedArray
that uses an initializer_list
for initialization. It stores a fixed number of elements in an internal array.
#include <initializer_list>
#include <iostream>
using namespace std;
template<class T, int size>
class FixedArray {
T arr[size]{};
size_t mysize = 0;
public:
// Constructor that initializes the array with a list of values
FixedArray(initializer_list<T> list) {
for (auto data : list) {
arr[mysize] = data;
mysize++;
}
}
void Show() const {
for (size_t x = 0; x < mysize; x++) {
cout << arr[x] << " ";
}
cout << endl;
}
// Overloaded operator[] to access elements by index
T& operator[](int index) {
return arr[index];
}
};
int main() {
// Initialize the FixedArray with a list of integers
FixedArray<int, 20> myarray{1, 3, 4, 6, 2, 6, 2, 6};
myarray.Show(); // Displays the elements
return 0;
}
Key Takeaways:
initializer_list
is ideal for passing multiple values in curly braces.
- Fixed arrays are straightforward but have a limitation on size (e.g.,
20
in this example).
Dynamic Data Structures Overview
When dealing with dynamic data structures, you decide how and where data is stored in memory—often on the heap—allowing flexible sizing and more complex behaviors. Common examples include:
- Stack (FILO: First In, Last Out)
- Queue (FIFO: First In, First Out)
- Dequeue (Double-Ended Queue)
- Priority Queue
- Linked List (Singly Linked, Doubly Linked, etc.)
- Trees (Binary, AVL, Red-Black, etc.)
Stack vs. Queue
- A Stack inserts/pushes elements on top and pops them from the top (Last element in → first out).
- A Queue enqueues elements from the back and dequeues from the front (First element in → first out).
Implementing a Custom Stack
Below is a simple Stack class that dynamically grows as items are pushed. This implementation uses templates to store any data type.
#include <iostream>
#include <assert.h>
using namespace std;
template <class T, int sizeLimit>
class Stack {
private:
T* data;
size_t size;
public:
Stack() : data(nullptr), size(0) {}
void push(const T& value) {
cout << "Value added successfully!" << endl;
// Create a new array with one extra space
T* newarr = new T[size + 1] {};
for (size_t x = 0; x < size; x++) {
newarr[x] = data[x];
}
newarr[size] = value;
// Clean up old memory
if (size != 0) {
delete[] data;
}
data = newarr;
newarr = nullptr;
size++;
}
T pop() {
assert(size > 0 && "Cannot pop from an empty stack!");
// Create a smaller array
T* newarr = new T[size - 1] {};
for (size_t x = 0; x < size - 1; x++) {
newarr[x] = data[x];
}
// Extract the last element
T last = data[size - 1];
delete[] data;
data = newarr;
newarr = nullptr;
size--;
return last;
}
// Returns the last element without removing it
T& peek() {
assert(size > 0 && "Cannot peek at an empty stack!");
return data[size - 1];
}
T& operator[](int index) {
return data[index];
}
size_t GetSize() const {
return size;
}
void Clear() {
if (data == nullptr && size == 0) {
return;
}
delete[] data;
data = nullptr;
size = 0;
}
~Stack() {
delete[] data;
}
};
// Class to demonstrate storing objects in the stack
class User {
string name;
string surname;
public:
User() {}
User(const string& name, const string& surname) {
this->name = name;
this->surname = surname;
}
void ShowUser() const {
cout << "===== USER =====" << endl;
cout << "Name: " << this->name << endl;
cout << "Surname: " << this->surname << endl;
}
};
int main() {
Stack<int, 10> mystack;
mystack.push(10);
mystack.push(11);
mystack.push(12);
// Pop and display the last value
// int lastint = mystack.pop();
// cout << "Last int : " << lastint << endl;
Stack<char, 10> mystack2;
mystack2.push('A');
mystack2.push('B');
mystack2.push('C');
char lastchar = mystack2.pop();
cout << "Last char : " << lastchar << endl;
Stack<User, 3> mystack3;
User u1("Tofiq", "Tofiqli");
User u2("A", "Ali");
User u3("John", "Johnlu");
mystack3.push(u1);
mystack3.push(u2);
mystack3.push(u3);
User lastuser = mystack3.pop();
cout << "Last User: " << endl;
lastuser.ShowUser();
return 0;
}
Highlights:
push()
dynamically resizes the underlying array, copying over old elements, then adding the new one.
pop()
removes the last element and decreases the array size by one.
- We can store any data type—including user-defined objects.
Implementing a Custom Queue
A Queue processes elements in the order they arrive (FIFO: First In, First Out). Here’s an example implementation using an array for storage:
#include <iostream>
#include <assert.h>
#include <windows.h> // For Sleep()
using namespace std;
template <class T>
class Queue {
T* arr;
int capacity;
int front;
int rear;
int count;
public:
Queue(int size) {
arr = new T[size] {};
capacity = size;
front = 0;
rear = -1;
count = 0;
}
void enqueue(T value) {
assert(!IsFull() && "Queue is full!");
arr[++rear] = value;
++count;
}
void dequeue() {
assert(!IsEmpty() && "Queue is empty!");
// Shift elements to remove the front
auto temp = new T[count - 1] {};
for (int x = 0; x < count - 1; x++) {
temp[x] = arr[x + 1];
}
delete[] arr;
arr = temp;
temp = nullptr;
--count;
rear--;
}
T peek() {
assert(!IsEmpty() && "Queue is empty!");
return arr[front];
}
bool IsFull() {
return size() == capacity;
}
bool IsEmpty() const {
return size() == 0;
}
int size() const {
return count;
}
~Queue() {
delete[] arr;
}
};
// Example class to enqueue
class Person {
string name;
string surname;
double money;
public:
Person() = default;
Person(const string& name, const string& surname, double money) {
SetName(name);
SetSurname(surname);
SetMoney(money);
}
void SetName(const string& name) { this->name = name; }
void SetSurname(const string& surname) { this->surname = surname; }
void SetMoney(double money) { this->money = money; }
string GetName() const { return name; }
string GetSurname() const { return surname; }
double GetMoney() const { return money; }
friend ostream& operator<<(ostream& out, const Person& p) {
out << "======= PERSON INFO =======" << endl;
out << "Name: " << p.GetName() << endl;
out << "Surname: " << p.GetSurname() << endl;
out << "Money: " << p.GetMoney() << endl;
return out;
}
};
int main() {
Person p1("John", "Johnlu", 1000);
Person p2("Mike", "Eliyev", 200);
Person p3("Hakuna", "Matata", 100);
// Create a Queue with capacity 3
Queue<Person> persons(3);
persons.enqueue(p1);
persons.enqueue(p2);
persons.enqueue(p3);
// Process the queue
while (!persons.IsEmpty()) {
auto person = persons.peek();
cout << person << endl;
Sleep(1500); // Pause for 1.5 seconds
cout << "His work finished\n" << endl;
persons.dequeue();
}
return 0;
}
Key Aspects:
enqueue()
adds an element to the end.
dequeue()
removes an element from the front.
peek()
returns the front element without removing it.
- We shift elements in an array approach, which is simple to grasp but not the most efficient for large queues (linked-list or circular buffer might be more efficient).
Other Data Structures
While Stacks and Queues are fundamental, you can also explore:
- Dequeue (Double-Ended Queue):
- Elements can be added/removed from both the front and the back.
- Priority Queue:
- Elements are ordered by priority. The highest (or lowest) priority gets dequeued first.
- Linked Lists:
- Nodes are dynamically allocated; easy insertion and deletion at any position.
- Binary Trees and beyond.
Reference
The content in this document is based on the original notes provided in Azerbaijani. For further details, you can refer to the original document using the following link:
Original Note - Azerbaijani Version