STL Containers

STL, which stands for Standard Template Library, is a powerful set of C++ template classes to provide common data structures and functions such as lists, stacks, arrays, etc. These are extremely handy for C++ programmers as they offer efficient, tested, and standardized implementations of various data structures.

What are STL Containers?

STL containers are pre-defined classes in C++ that store collections of objects. Containers manage the storage space for objects and provide member functions to access and manipulate them. They are templated, meaning you can use them for various data types without rewriting code.

Types of Containers

STL containers are categorized into:

Sequence Containers: Store data in a linear order.

Examples: vector, deque, list, array, forward_list

Associative Containers: Store data as key-value pairs, enabling quick search.

Examples: set, multiset, map, multimap

Derived Containers (Adapters): Provide specific functionality based on existing containers.

Examples: stack, queue, priority_queue

why use STL containers ?

  1. Standardization: STL containers are part of the C++ Standard Library, ensuring their availability across different compilers and platforms. This standardization promotes code portability and interoperability.

  2. Efficiency: STL containers are often highly optimized, providing efficient implementations for common operations like insertion, deletion, and retrieval. They are typically designed to strike a balance between speed and memory usage.

  3. Versatility: STL offers a wide range of containers catering to diverse programming needs. Whether you need dynamic arrays (vectors), linked lists (lists), associative arrays (maps), or stacks/queues, there’s a container available.

  4. Safety: Many STL containers provide bounds-checking mechanisms to prevent buffer overflows and memory corruption issues, enhancing code safety.

  5. Ease of Use: STL containers come with a rich set of member functions and algorithms, making them easy to work with. They also support iterators, allowing for convenient traversal and manipulation of elements.

  6. Template-based: STL containers are implemented using templates, allowing for generic programming. This means you can use them with any data type without having to rewrite the container code.

  7. Widely Adopted: STL containers are widely used in the C++ community, which means there is extensive documentation, tutorials, and community support available. This makes it easier for developers to learn and use them effectively.

  8. Time-saving: By leveraging STL containers, developers can avoid reinventing the wheel and focus more on solving the core problem at hand. STL containers provide tested and efficient implementations of common data structures, saving development time.

Disadvantages

  1. Overhead: STL containers may introduce some overhead due to their generic nature and additional functionality. In performance-critical scenarios, handcrafted solutions might offer better performance.

  2. Learning Curve: Mastering STL containers and their associated algorithms may require some learning upfront, especially for beginners. Understanding concepts like iterators, algorithms, and container intricacies can take time.

  3. Limited Customization: While STL containers offer a wide range of functionality, they may not always fit specific requirements perfectly. Customization options are limited compared to handcrafted data structures.

  4. Complexity: Some STL containers, especially associative containers like maps and sets, have complex internal implementations. Understanding their underlying data structures and algorithms may require advanced knowledge of computer science concepts.

  5. Debugging: Debugging code that heavily utilizes STL containers can sometimes be challenging, especially when dealing with complex data structures and algorithms.

  6. Fragmentation: Different compilers and library implementations may have slight differences in behavior or performance characteristics for certain STL containers, leading to potential compatibility issues.

Sequence Containers

Sequence containers in C++ are data structures that store elements in a sequential manner, allowing for efficient access to elements by their position. The key sequence containers in STL include vector, list, and deque.

Vector

A vector is a dynamic array that automatically resizes itself when needed, providing fast access to elements. It’s one of the most commonly used containers due to its versatility and efficiency.

				
					#include <iostream>
#include <vector>

int main() {
    // Creating a vector of integers
    std::vector<int> vec;

    // Adding elements to the vector
    vec.push_back(10);  // Adds element 10 at the end
    vec.push_back(20);  // Adds element 20 at the end
    vec.push_back(30);  // Adds element 30 at the end

    // Accessing elements
    std::cout << "Elements of the vector: ";
    for (size_t i = 0; i < vec.size(); ++i) {
        std::cout << vec[i] << " ";  // Accessing elements using index
    }
    std::cout << std::endl;

    // Size of the vector
    std::cout << "Size of the vector: " << vec.size() << std::endl;

    // Capacity of the vector
    std::cout << "Capacity of the vector: " << vec.capacity() << std::endl;

    // Accessing first and last elements
    std::cout << "First element: " << vec.front() << std::endl;
    std::cout << "Last element: " << vec.back() << std::endl;

    // Checking if the vector is empty
    std::cout << "Is the vector empty? " << (vec.empty() ? "Yes" : "No") << std::endl;

    // Clearing the vector
    vec.clear();
    std::cout << "Size of the vector after clearing: " << vec.size() << std::endl;

    // Resizing the vector
    vec.resize(5, 100);  // Resizes the vector to size 5 and fills with value 100
    std::cout << "Elements of the resized vector: ";
    for (int i : vec) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    return 0;
}

				
			
				
					// output //
Elements of the vector: 10 20 30 
Size of the vector: 3
Capacity of the vector: 3
First element: 10
Last element: 30
Is the vector empty? No
Size of the vector after clearing: 0
Elements of the resized vector: 100 100 100 100 100 

				
			

Explanation:

  • We create a vector vec of integers.
  • Using push_back(), we add elements 10, 20, and 30 to the vector.
  • We access elements using both index (vec[i]) and front()/back() functions.
  • We check the size of the vector using the size() function and its capacity using the capacity() function.
  • We check if the vector is empty using the empty() function.
  • We clear the vector using the clear() function.
  • We resize the vector to size 5 and fill it with the value 100 using the resize() function.
  • Finally, we output the resized vector’s elements using a range-based for loop.

All Possible Operations

Construction and Initialization

  • vector() – Constructs an empty vector.
  • vector(size_type count) – Constructs a vector with count copies of a default-initialized value.
  • vector(size_type count, const T& value) – Constructs a vector with count copies of a specified value.
  • vector(const vector& other) – Constructs a vector as a copy of another vector.
  • vector(std::initializer_list<T> init) – Constructs a vector with the elements from the initializer list.

Element Access

  • operator[] – Accesses the element at the specified position.
  • at() – Accesses the element at the specified position with bounds checking.
  • front() – Accesses the first element.
  • back() – Accesses the last element.
  • data() – Returns a pointer to the underlying array.

Modifiers

  • assign() – Replaces the contents of the vector with new elements.
  • push_back() – Adds an element to the end of the vector.
  • pop_back() – Removes the last element of the vector.
  • insert() – Inserts elements into the vector at the specified position.
  • erase() – Removes elements from the vector at the specified position or range.
  • clear() – Removes all elements from the vector.
  • emplace() – Constructs and inserts elements into the vector.
  • emplace_back() – Constructs and appends an element to the end of the vector.
  • resize() – Resizes the vector to contain a specified number of elements.
  • swap() – Swaps the contents of two vectors.

Capacity

  • size() – Returns the number of elements in the vector.
  • max_size() – Returns the maximum possible number of elements the vector can hold.
  • capacity() – Returns the number of elements that can be held in the currently allocated storage.
  • reserve() – Increases the capacity of the vector to at least a specified amount.
  • shrink_to_fit() – Reduces the capacity of the vector to fit its size.

Iterators

  • begin() – Returns an iterator to the beginning of the vector.
  • end() – Returns an iterator to the end of the vector.
  • rbegin() – Returns a reverse iterator to the beginning of the vector.
  • rend() – Returns a reverse iterator to the end of the vector.

Miscellaneous

  • empty() – Checks if the vector is empty.
  • clear() – Removes all elements from the vector.
  • operator==, operator!=, operator<, operator<=, operator>, operator>= – Comparison operators for vectors.

Combined Example 

				
					#include <iostream>
#include <vector>

int main() {
    // Construction and Initialization
    std::vector<int> vec1;  // Constructs an empty vector
    std::vector<int> vec2(5, 10);  // Constructs a vector with 5 copies of value 10
    std::vector<int> vec3 = {1, 2, 3, 4, 5};  // Constructs a vector with initializer list

    // Element Access
    std::cout << "First element: " << vec3.front() << std::endl;  // Accesses the first element
    std::cout << "Last element: " << vec3.back() << std::endl;  // Accesses the last element
    std::cout << "Element at index 2: " << vec3[2] << std::endl;  // Accesses element at index 2
    std::cout << "Element at index 3: " << vec3.at(3) << std::endl;  // Accesses element at index 3

    // Modifiers
    vec1.push_back(20);  // Adds an element to the end
    vec1.insert(vec1.begin() + 1, 30);  // Inserts an element at the specified position
    vec1.pop_back();  // Removes the last element
    vec1.erase(vec1.begin());  // Removes the first element
    vec1.emplace_back(40);  // Constructs and appends an element to the end
    vec1.clear();  // Removes all elements

    // Capacity
    std::cout << "Size of vec2: " << vec2.size() << std::endl;  // Returns the number of elements
    std::cout << "Capacity of vec2: " << vec2.capacity() << std::endl;  // Returns the capacity
    vec2.reserve(10);  // Increases the capacity to at least 10
    vec2.shrink_to_fit();  // Reduces the capacity to fit the size

    // Iterators
    for (auto it = vec3.begin(); it != vec3.end(); ++it) {  // Using iterators to iterate through elements
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // Miscellaneous
    std::cout << "Is vec1 empty? " << (vec1.empty() ? "Yes" : "No") << std::endl;  // Checks if the vector is empty
    std::vector<int> vec4 = {1, 2, 3};
    std::vector<int> vec5 = {1, 2, 3};
    std::cout << "vec4 == vec5: " << (vec4 == vec5 ? "true" : "false") << std::endl;  // Comparison operators for vectors

    return 0;
}

				
			
				
					// output //
First element: 1
Last element: 5
Element at index 2: 3
Element at index 3: 4
Size of vec2: 5
Capacity of vec2: 5
1 2 3 4 5 
Is vec1 empty? Yes
vec4 == vec5: true

				
			

List

A list is a doubly linked list where each element has a pointer to the next and previous elements. It allows for fast insertion and deletion operations anywhere in the container.

				
					#include <iostream>
#include <list>

int main() {
    // Creating a list of integers
    std::list<int> myList;

    // 1. Adding elements to the list
    myList.push_back(10);
    myList.push_front(5);
    myList.push_back(20);
    myList.push_front(2);

    // 2. Accessing elements
    std::cout << "Elements of the list: ";
    for (int i : myList) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    // 3. Removing elements
    myList.pop_front(); // Removes the first element
    myList.pop_back();  // Removes the last element

    // 4. Inserting elements
    auto it = myList.begin();
    std::advance(it, 2); // Advance iterator to the third element
    myList.insert(it, 15); // Insert 15 before the third element

    // 5. Erasing elements
    it = myList.begin();
    std::advance(it, 1); // Advance iterator to the second element
    myList.erase(it); // Erase the second element

    // 6. Checking if the list is empty
    std::cout << "Is the list empty? " << (myList.empty() ? "Yes" : "No") << std::endl;

    // 7. Clearing the list
    myList.clear();

    // Outputting the size of the list after clearing
    std::cout << "Size of the list after clearing: " << myList.size() << std::endl;

    return 0;
}

				
			
				
					// output //
Elements of the list: 2 5 10 15 
Is the list empty? No
Size of the list after clearing: 0

				
			

Explanation:

  1. Adding elements: We use push_back() and push_front() to add elements 10 and 5 at the back and front of the list respectively.
  2. Accessing elements: We iterate through the list using a range-based for loop to print its elements.
  3. Removing elements: We use pop_front() and pop_back() to remove the first and last elements of the list respectively.
  4. Inserting elements: We use insert() to insert the value 15 before the third element of the list.
  5. Erasing elements: We use erase() to erase the second element of the list.
  6. Checking if the list is empty: We use the empty() member function to check if the list is empty.
  7. Clearing the list: We use clear() to remove all elements from the list.

All possible operations

Construction/Initialization

  • std::list<Type> mylist; – Creates an empty list of elements of type Type.
  • std::list<Type> mylist (size, value); – Creates a list with size elements, each initialized to value.
  • std::list<Type> mylist (anotherList); – Creates a copy of anotherList.

Element Access

  • front() – Accesses the first element.
  • back() – Accesses the last element.

Iterators

  • begin() – Returns an iterator to the beginning.
  • end() – Returns an iterator to the end.
  • rbegin() – Returns a reverse iterator to the beginning.
  • rend() – Returns a reverse iterator to the end.

Modifiers

  • push_back(value) – Adds a new element at the end.
  • pop_back() – Removes the last element.
  • push_front(value) – Adds a new element at the beginning.
  • pop_front() – Removes the first element.
  • insert(iterator, value) – Inserts a new element before the element at the specified position.
  • erase(iterator) – Removes the element at the specified position.
  • clear() – Removes all elements from the list.
  • resize(size) – Resizes the list to contain size elements.
  • swap(list) – Swaps the contents of two lists.

Capacity

  • empty() – Checks if the list is empty.
  • size() – Returns the number of elements in the list.

Operations

  • merge(list) – Merges another list into this one.
  • splice(iterator, list) – Moves elements from another list into this list.
  • remove(value) – Removes all elements equal to value.
  • remove_if(predicate) – Removes all elements for which the predicate returns true.
  • reverse() – Reverses the order of elements in the list.
  • unique() – Removes consecutive duplicate elements.
  • sort() – Sorts the elements of the list.

Combined example

				
					#include <iostream>
#include <list>

int main() {
    // Construction/Initialization
    std::list<int> mylist {1, 2, 3, 4, 5};
    
    // Element Access
    std::cout << "First element: " << mylist.front() << std::endl;
    std::cout << "Last element: " << mylist.back() << std::endl;
    
    // Iterators
    std::cout << "Elements:";
    for(auto it = mylist.begin(); it != mylist.end(); ++it) {
        std::cout << " " << *it;
    }
    std::cout << std::endl;
    
    // Modifiers
    mylist.push_back(6);
    mylist.pop_back();
    mylist.push_front(0);
    mylist.pop_front();
    mylist.insert(++mylist.begin(), 10);
    mylist.erase(--mylist.end());
    
    // Capacity
    std::cout << "Size of list: " << mylist.size() << std::endl;
    std::cout << "Is list empty? " << (mylist.empty() ? "Yes" : "No") << std::endl;
    
    // Operations
    std::list<int> anotherList {7, 8, 9};
    mylist.merge(anotherList);
    mylist.remove(3);
    mylist.remove_if([](int x) { return x < 5; });
    mylist.reverse();
    mylist.unique();
    mylist.sort();
    
    // Output
    std::cout << "Modified list:";
    for(const auto& elem : mylist) {
        std::cout << " " << elem;
    }
    std::cout << std::endl;
    
    return 0;
}

				
			
				
					// output //
First element: 1
Last element: 5
Elements: 1 2 3 4 5
Size of list: 5
Is list empty? No
Modified list: 4 5 7 8 9 10

				
			

Explanation:

  • The code first constructs a list mylist with elements 1, 2, 3, 4, 5.
  • Element access operations (front() and back()) are used to retrieve the first and last elements of the list.
  • Iterators (begin() and end()) are used in a loop to print all elements of the list.
  • Various modifiers (push_back(), pop_back(), push_front(), pop_front(), insert(), erase()) are applied to modify the list.
  • The size() and empty() functions demonstrate capacity operations to check the size and emptiness of the list.
  • Operations like merge(), remove(), remove_if(), reverse(), unique(), and sort() are performed on the list to manipulate its contents.
  • Finally, the modified list is printed using a range-based for loop.

Deque

A deque (double-ended queue) is similar to a vector but allows fast insertion and deletion at both ends. It’s useful when you need efficient insertion and deletion at the beginning and end of the sequence.

				
					#include <iostream>
#include <deque>

int main() {
    // Creating a deque of integers
    std::deque<int> myDeque;

    // Checking if the deque is empty
    std::cout << "Is deque empty? " << (myDeque.empty() ? "Yes" : "No") << std::endl;

    // Adding elements to the front and back of the deque
    myDeque.push_front(10);
    myDeque.push_back(20);
    myDeque.push_front(5);

    // Accessing the front and back elements of the deque
    std::cout << "Front element of the deque: " << myDeque.front() << std::endl;
    std::cout << "Back element of the deque: " << myDeque.back() << std::endl;

    // Outputting elements of the deque
    std::cout << "Elements of the deque: ";
    for (int i : myDeque) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    // Outputting the size of the deque
    std::cout << "Size of the deque: " << myDeque.size() << std::endl;

    // Removing elements from the front and back of the deque
    myDeque.pop_front();
    myDeque.pop_back();

    // Outputting elements of the deque after removal
    std::cout << "Elements of the deque after removal: ";
    for (int i : myDeque) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    // Clearing the deque
    myDeque.clear();

    // Checking if the deque is empty after clearing
    std::cout << "Is deque empty after clearing? " << (myDeque.empty() ? "Yes" : "No") << std::endl;

    return 0;
}

				
			
				
					// output //
Is deque empty? Yes
Front element of the deque: 5
Back element of the deque: 20
Elements of the deque: 5 10 20 
Size of the deque: 3
Elements of the deque after removal: 10 
Is deque empty after clearing? Yes

				
			

Explanation:

  • We start by creating an empty deque myDeque.
  • We use the empty() function to check if the deque is empty.
  • Elements 10 and 20 are added to the deque using push_front() and push_back() respectively, and 5 is added to the front.
  • We access the front and back elements of the deque using front() and back() functions.
  • We iterate through the deque using a range-based for loop to output its elements.
  • We output the size of the deque using the size() function.
  • We remove elements from the front and back of the deque using pop_front() and pop_back() respectively.
  • We clear the deque using the clear() function.
  • Finally, we use the empty() function again to check if the deque is empty after clearing.

All Possible operations

  1. Constructor: Creates an empty deque or initializes it with elements.

  2. Destructor: Destroys all elements in the deque and deallocates the memory allocated by the container.

  3. push_back(): Adds an element to the end of the deque.

  4. push_front(): Adds an element to the front of the deque.

  5. pop_back(): Removes the last element from the deque.

  6. pop_front(): Removes the first element from the deque.

  7. front(): Returns a reference to the first element in the deque.

  8. back(): Returns a reference to the last element in the deque.

  9. begin(): Returns an iterator to the first element in the deque.

  10. end(): Returns an iterator to the position just past the last element in the deque.

  11. rbegin(): Returns a reverse iterator pointing to the last element in the deque (reverse beginning).

  12. rend(): Returns a reverse iterator pointing to the position before the first element in the deque (reverse end).

  13. empty(): Returns true if the deque is empty, false otherwise.

  14. size(): Returns the number of elements in the deque.

  15. clear(): Removes all elements from the deque, leaving it with a size of 0.

  16. erase(): Removes elements from the deque based on a specified range or position.

  17. insert(): Inserts elements into the deque at a specified position.

  18. operator[]: Accesses elements of the deque by index.

Combined Example

				
					#include <iostream>
#include <deque>

int main() {
    // Constructor: Creating an empty deque
    std::deque<int> myDeque;

    // push_back(): Adding elements to the end
    myDeque.push_back(10);
    myDeque.push_back(20);
    myDeque.push_back(30);

    // push_front(): Adding elements to the front
    myDeque.push_front(5);
    myDeque.push_front(2);

    // Output the elements of the deque
    std::cout << "Deque elements:";
    for (auto elem : myDeque) {
        std::cout << " " << elem;
    }
    std::cout << std::endl;

    // pop_back(): Removing the last element
    myDeque.pop_back();

    // pop_front(): Removing the first element
    myDeque.pop_front();

    // front(): Accessing the first element
    std::cout << "First element: " << myDeque.front() << std::endl;

    // back(): Accessing the last element
    std::cout << "Last element: " << myDeque.back() << std::endl;

    // begin() and end(): Iterating through the deque
    std::cout << "Iterating through deque:";
    for (auto it = myDeque.begin(); it != myDeque.end(); ++it) {
        std::cout << " " << *it;
    }
    std::cout << std::endl;

    // empty() and size(): Checking if deque is empty and its size
    if (myDeque.empty()) {
        std::cout << "Deque is empty" << std::endl;
    } else {
        std::cout << "Deque size: " << myDeque.size() << std::endl;
    }

    // clear(): Clearing all elements
    myDeque.clear();
    std::cout << "Deque size after clearing: " << myDeque.size() << std::endl;

    return 0;
}

				
			
				
					// output //
Deque elements: 2 5 10 20 30
First element: 5
Last element: 30
Iterating through deque: 2 5 10 20 30
Deque size: 5
Deque size after clearing: 0

				
			

Explanation:

  • The code first creates an empty deque myDeque.
  • Elements are added to the deque using push_back() and push_front() operations.
  • The elements of the deque are outputted to the console.
  • The last element is removed using pop_back() and the first element is removed using pop_front().
  • The first and last elements are accessed using front() and back() respectively.
  • The elements of the deque are iterated through using iterators obtained from begin() and end().
  • The size of the deque is checked using empty() and size().
  • All elements are cleared from the deque using clear().

Associative Containers

In the Standard Template Library (STL), Associative Containers are a group of containers that store data in an ordered structure using keys. These containers provide efficient insertion, deletion, and search operations. The order of the elements is determined based on their keys.

Overview of Associative Containers

There are four types of associative containers in STL:

  1. set: Stores unique elements in sorted order.
  2. multiset: Stores duplicate elements in sorted order.
  3. map: Stores key-value pairs where keys are unique and sorted.
  4. multimap: Stores key-value pairs where keys can have duplicates, in sorted order.

All associative containers are implemented using Self-Balancing Binary Search Trees (like Red-Black Trees), which ensure logarithmic time complexity (O(log n)) for search, insertion, and deletion.

Operations in Associative Containers

The following operations are common across all associative containers:

  1. Insertion: Add new elements into the container.
  2. Deletion: Remove existing elements.
  3. Search: Find specific elements based on a key or value.
  4. Traversal: Iterate through all elements.
  5. Size: Get the number of elements.
  6. Count: Find the number of occurrences of a key.
  7. Lower Bound and Upper Bound: Find the range of keys.
  8. Clear: Remove all elements.

set – Code Examples and Operations

What is a set?

A set stores unique elements in sorted order. If you try to insert duplicates, they will be ignored.

Operations in set

OperationDescription
insert()Inserts an element
erase()Deletes an element
find()Searches for an element
count()Returns 1 if key exists, 0 otherwise
begin() / end()Iterators to traverse the set
lower_bound() / upper_bound()Find range for a key
clear()Removes all elements

Code Example: All Operations in set

				
					#include <iostream>
#include <set>

int main() {
    std::set<int> mySet;

    // 1. Insertion
    mySet.insert(10);
    mySet.insert(20);
    mySet.insert(30);
    mySet.insert(20); // Duplicate, will be ignored

    std::cout << "Set after insertion: ";
    for (int x : mySet) {
        std::cout << x << " ";
    }
    std::cout << "\n";

    // 2. Find and Count
    if (mySet.find(20) != mySet.end()) {
        std::cout << "20 is found in the set.\n";
    }
    std::cout << "Count of 30: " << mySet.count(30) << "\n";

    // 3. Erase
    mySet.erase(10);
    std::cout << "Set after erasing 10: ";
    for (int x : mySet) {
        std::cout << x << " ";
    }
    std::cout << "\n";

    // 4. Lower Bound and Upper Bound
    std::cout << "Lower bound of 20: " << *mySet.lower_bound(20) << "\n";
    std::cout << "Upper bound of 20: " << *mySet.upper_bound(20) << "\n";

    // 5. Clear
    mySet.clear();
    std::cout << "Set size after clear: " << mySet.size() << "\n";

    return 0;
}

				
			

Output

				
					Set after insertion: 10 20 30 
20 is found in the set.
Count of 30: 1
Set after erasing 10: 20 30 
Lower bound of 20: 20
Upper bound of 20: 30
Set size after clear: 0

				
			

Explanation:

  1. Elements are inserted in sorted order.
  2. Duplicates are ignored.
  3. find() checks if an element exists.
  4. lower_bound() and upper_bound() help find range.

multiset – Code Examples and Operations

What is a multiset?

A multiset allows duplicate elements and maintains them in sorted order.

Operations in multiset

The operations are similar to set:

  • insert()
  • erase()
  • find()
  • count()
  • begin() / end()
  • clear()

Code Example: All Operations in multiset

				
					#include <iostream>
#include <set>

int main() {
    std::multiset<int> myMultiSet;

    // 1. Insertion
    myMultiSet.insert(10);
    myMultiSet.insert(20);
    myMultiSet.insert(10); // Duplicate allowed

    std::cout << "Multiset after insertion: ";
    for (int x : myMultiSet) {
        std::cout << x << " ";
    }
    std::cout << "\n";

    // 2. Count
    std::cout << "Count of 10: " << myMultiSet.count(10) << "\n";

    // 3. Erase all occurrences of a key
    myMultiSet.erase(10);
    std::cout << "Multiset after erasing 10: ";
    for (int x : myMultiSet) {
        std::cout << x << " ";
    }
    std::cout << "\n";

    return 0;
}

				
			

Output

				
					Multiset after insertion: 10 10 20 
Count of 10: 2
Multiset after erasing 10: 20 

				
			

Explanation:

  1. multiset allows duplicate values.
  2. count() returns how many times a value appears.
  3. erase() removes all occurrences of a key.

map – Code Examples and Operations

What is a map?

A map is a container that stores key-value pairs in sorted order. Keys are unique.

Operations in map

OperationDescription
insert({key, value})Inserts a key-value pair
erase(key)Removes a key-value pair
find(key)Searches for a key
at(key)Access value at a key
count(key)Returns 1 if key exists
clear()Removes all key-value pairs

Code Example: All Operations in map

				
					#include <iostream>
#include <map>

int main() {
    std::map<int, std::string> myMap;

    // 1. Insertion
    myMap.insert({1, "One"});
    myMap[2] = "Two"; // Alternative way to insert

    // 2. Displaying map
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << "\n";
    }

    // 3. Accessing value
    std::cout << "Value at key 1: " << myMap.at(1) << "\n";

    // 4. Erase
    myMap.erase(2);
    std::cout << "After erasing key 2:\n";
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << "\n";
    }

    return 0;
}

				
			

Output

				
					1: One
2: Two
Value at key 1: One
After erasing key 2:
1: One

				
			

Explanation:

  1. insert() or [] can insert elements.
  2. at() accesses values safely.
  3. erase() removes key-value pairs.

multimap – Code Examples and Operations

What is a multimap?

A multimap is an associative container that stores key-value pairs where duplicate keys are allowed. Unlike a map, multiple entries can exist with the same key, and all keys remain sorted.

Key Properties of multimap:

  1. Duplicate Keys: A multimap allows multiple values for the same key.
  2. Sorted Order: Elements are stored in ascending order based on keys.
  3. Self-Balancing Binary Search Tree: Implemented as a Red-Black Tree.
  4. No Random Access: Unlike arrays or vectors, elements in multimap cannot be accessed directly using an index.

Operations in multimap

The following operations are supported by multimap:

OperationDescription
insert({key, value})Inserts a key-value pair into the multimap
erase(key)Erases all elements with a specific key
find(key)Returns an iterator to the first occurrence of the key
count(key)Returns the number of occurrences of a key
equal_range(key)Returns a range (pair of iterators) for a key
lower_bound(key)Returns iterator to the first key ≥ given key
upper_bound(key)Returns iterator to the first key > given key
begin() / end()Returns iterators to traverse the multimap
clear()Removes all elements

Code Example: All Operations in multimap

				
					#include <iostream>
#include <map>

int main() {
    // Declare a multimap
    std::multimap<int, std::string> myMultiMap;

    // 1. Insert elements
    myMultiMap.insert({1, "Apple"});
    myMultiMap.insert({2, "Banana"});
    myMultiMap.insert({1, "Orange"});  // Duplicate key
    myMultiMap.insert({3, "Mango"});

    std::cout << "Contents of multimap:\n";
    for (const auto& pair : myMultiMap) {
        std::cout << pair.first << " => " << pair.second << "\n";
    }

    // 2. Count occurrences of a key
    std::cout << "\nCount of key 1: " << myMultiMap.count(1) << "\n";

    // 3. Find all values for a specific key (key = 1)
    std::cout << "\nValues with key 1:\n";
    auto range = myMultiMap.equal_range(1);
    for (auto it = range.first; it != range.second; ++it) {
        std::cout << it->first << " => " << it->second << "\n";
    }

    // 4. Erase all values with key 1
    myMultiMap.erase(1);
    std::cout << "\nMultimap after erasing key 1:\n";
    for (const auto& pair : myMultiMap) {
        std::cout << pair.first << " => " << pair.second << "\n";
    }

    // 5. Using lower_bound and upper_bound
    myMultiMap.insert({2, "Pineapple"});
    myMultiMap.insert({2, "Grapes"});

    std::cout << "\nLower bound of key 2: " << myMultiMap.lower_bound(2)->second << "\n";
    std::cout << "Upper bound of key 2: " << myMultiMap.upper_bound(2)->second << "\n";

    return 0;
}

				
			

Output

				
					Contents of multimap:
1 => Apple
1 => Orange
2 => Banana
3 => Mango

Count of key 1: 2

Values with key 1:
1 => Apple
1 => Orange

Multimap after erasing key 1:
2 => Banana
3 => Mango

Lower bound of key 2: Banana
Upper bound of key 2: Mango

				
			

Explanation of Code

Insertion:

  • Elements with duplicate keys (1 => "Apple", 1 => "Orange") are stored in sorted order based on the key.
  • Use insert() to add key-value pairs.

Traversal:

  • The for loop traverses the multimap using iterators.

Finding a Range:

  • equal_range(1) returns a pair of iterators (start and end) for all elements with key 1.

Erase:

  • erase(1) removes all key-value pairs where the key is 1.

Lower Bound and Upper Bound:

  • lower_bound(2) returns the first occurrence of key 2.
  • upper_bound(2) returns the first key greater than 2 (key 3 in this case).

Unordered Containers

Unordered containers are a group of associative containers in the Standard Template Library (STL) that store elements in no particular order. Instead of sorting, they use hash tables for organizing and retrieving elements efficiently.

Types of Unordered Containers

ContainerDescription
unordered_mapKey-value pairs with unique keys.
unordered_multimapKey-value pairs where duplicate keys are allowed.
unordered_setUnique keys only (no duplicates).
unordered_multisetAllows duplicate keys (similar to multiset but unordered).

Key Features of Unordered Containers

  1. Hash Table Based: Internally implemented using hash tables.
  2. No Specific Order: Elements are not stored in a sorted order.
  3. O(1) Average Access Time: Fast lookups, insertions, and deletions.
  4. Keys Must Be Hashable: Custom objects need a defined hash function.
  5. Load Factor and Buckets: Hash tables distribute elements into buckets.

unordered_map – Example and Operations

An unordered_map is an associative container that stores key-value pairs where keys are unique.

Supported Operations

OperationDescription
insert(key, value)Inserts a key-value pair into the map. If the key already exists, the insertion is ignored.
erase(key)Removes the element with the given key.
find(key)Returns an iterator to the element with the given key. If not found, returns end().
count(key)Returns the number of elements matching the given key (always 0 or 1 for unordered_map).
operator[]Access or insert a key-value pair. If the key does not exist, it inserts it with a default value.
at(key)Returns the value associated with the key. Throws an exception if the key is not found.
clear()Removes all elements from the map.
empty()Returns true if the map is empty.
size()Returns the number of elements in the map.
bucket_count()Returns the number of buckets in the hash table.
bucket_size(bucket)Returns the number of elements in a specific bucket.
load_factor()Returns the load factor of the map (number of elements divided by the number of buckets).
rehash(n)Resizes the hash table to have at least n buckets.

Code Example

				
					#include <iostream>
#include <unordered_map>
#include <string>

int main() {
    // Declare an unordered_map
    std::unordered_map<std::string, int> studentMarks;

    // 1. Insert elements
    studentMarks["Alice"] = 90;
    studentMarks["Bob"] = 85;
    studentMarks.insert({"Charlie", 95});
    studentMarks.insert({"David", 88});

    // 2. Print the unordered_map
    std::cout << "Contents of unordered_map:\n";
    for (const auto& pair : studentMarks) {
        std::cout << pair.first << " => " << pair.second << "\n";
    }

    // 3. Find an element
    std::string key = "Charlie";
    if (studentMarks.find(key) != studentMarks.end()) {
        std::cout << "\n" << key << " found with marks: " << studentMarks[key] << "\n";
    }

    // 4. Count an element
    std::cout << "\nCount of key 'Bob': " << studentMarks.count("Bob") << "\n";

    // 5. Erase an element
    studentMarks.erase("David");
    std::cout << "\nAfter erasing 'David':\n";
    for (const auto& pair : studentMarks) {
        std::cout << pair.first << " => " << pair.second << "\n";
    }

    // 6. Load factor and bucket count
    std::cout << "\nNumber of buckets: " << studentMarks.bucket_count();
    std::cout << "\nLoad factor: " << studentMarks.load_factor() << "\n";

    return 0;
}

				
			
				
					// Output
Contents of unordered_map:
Alice => 90
Bob => 85
Charlie => 95
David => 88

Charlie found with marks: 95

Count of key 'Bob': 1

After erasing 'David':
Alice => 90
Bob => 85
Charlie => 95

Number of buckets: 11
Load factor: 0.272727

				
			

Explanation

Insertion:

  • Insert key-value pairs using [] or insert().

Accessing Elements:

  • Use find() to check if a key exists.
  • count() determines if a key is present (returns 1 or 0).

Erasing Elements:

  • Use erase(key) to remove an element by key.

Load Factor:

  • The ratio of the number of elements to the number of buckets.

unordered_multimap – Example and Operations

An unordered_multimap allows duplicate keys.

Supported Operations

OperationDescription
insert(key, value)Inserts a key-value pair into the map. Duplicate keys are allowed.
erase(key)Removes all elements matching the given key.
find(key)Returns an iterator to the first element with the given key. If not found, returns end().
count(key)Returns the number of elements matching the given key.
equal_range(key)Returns a pair of iterators representing the range of elements with the same key.
clear()Removes all elements from the multimap.
empty()Returns true if the multimap is empty.
size()Returns the number of elements in the multimap.
bucket_count()Returns the number of buckets in the hash table.
bucket_size(bucket)Returns the number of elements in a specific bucket.
load_factor()Returns the load factor of the multimap.
rehash(n)Resizes the hash table to have at least n buckets.

Code Example

				
					#include <iostream>
#include <unordered_map>

int main() {
    // Declare an unordered_multimap
    std::unordered_multimap<int, std::string> myMultiMap;

    // Insert elements (duplicate keys allowed)
    myMultiMap.insert({1, "Apple"});
    myMultiMap.insert({1, "Orange"});
    myMultiMap.insert({2, "Banana"});
    myMultiMap.insert({3, "Mango"});

    std::cout << "Contents of unordered_multimap:\n";
    for (const auto& pair : myMultiMap) {
        std::cout << pair.first << " => " << pair.second << "\n";
    }

    // Find range of values for a key
    std::cout << "\nValues with key 1:\n";
    auto range = myMultiMap.equal_range(1);
    for (auto it = range.first; it != range.second; ++it) {
        std::cout << it->first << " => " << it->second << "\n";
    }

    return 0;
}

				
			
				
					// Output
Contents of unordered_multimap:
1 => Apple
1 => Orange
2 => Banana
3 => Mango

Values with key 1:
1 => Apple
1 => Orange

				
			

unordered_set – Example and Operations

An unordered_set contains unique keys.

Supported Operations

OperationDescription
insert(value)Inserts a unique value into the set. If the value already exists, the insertion is ignored.
erase(value)Removes the element matching the given value.
find(value)Returns an iterator to the element matching the value. If not found, returns end().
count(value)Returns the number of elements matching the given value (always 0 or 1 for unordered_set).
clear()Removes all elements from the set.
empty()Returns true if the set is empty.
size()Returns the number of elements in the set.
bucket_count()Returns the number of buckets in the hash table.
bucket_size(bucket)Returns the number of elements in a specific bucket.
load_factor()Returns the load factor of the set.
rehash(n)Resizes the hash table to have at least n buckets.

Code Example

				
					#include <iostream>
#include <unordered_set>

int main() {
    std::unordered_set<int> mySet;

    // Insert elements
    mySet.insert(10);
    mySet.insert(20);
    mySet.insert(10);  // Duplicate ignored
    mySet.insert(30);

    std::cout << "Contents of unordered_set:\n";
    for (const int& num : mySet) {
        std::cout << num << " ";
    }

    // Check if an element exists
    if (mySet.find(20) != mySet.end()) {
        std::cout << "\n\n20 exists in the set.";
    }

    return 0;
}

				
			
				
					// Output
Contents of unordered_set:
10 20 30 

20 exists in the set.


				
			

unordered_multiset – Example and Operations

An unordered_multiset allows duplicate keys.

Supported Operations

OperationDescription
insert(value)Inserts a value into the multiset. Duplicate values are allowed.
erase(value)Removes all elements matching the given value.
find(value)Returns an iterator to the first element matching the value. If not found, returns end().
count(value)Returns the number of elements matching the given value.
equal_range(value)Returns a pair of iterators representing the range of elements with the same value.
clear()Removes all elements from the multiset.
empty()Returns true if the multiset is empty.
size()Returns the number of elements in the multiset.
bucket_count()Returns the number of buckets in the hash table.
bucket_size(bucket)Returns the number of elements in a specific bucket.
load_factor()Returns the load factor of the multiset.
rehash(n)Resizes the hash table to have at least n buckets.

Code Example

				
					#include <iostream>
#include <unordered_set>

int main() {
    std::unordered_multiset<int> myMultiSet;

    // Insert elements (duplicates allowed)
    myMultiSet.insert(10);
    myMultiSet.insert(20);
    myMultiSet.insert(10);
    myMultiSet.insert(30);

    std::cout << "Contents of unordered_multiset:\n";
    for (const int& num : myMultiSet) {
        std::cout << num << " ";
    }

    return 0;
}

				
			
				
					// Output
Contents of unordered_multiset:
10 10 20 30

				
			

Derived containers in C++

Derived containers in C++ Standard Template Library (STL) are built using existing containers and provide additional functionalities. These containers are specifically tailored to suit specialized operations like priority management, queue-based data handling, or stack-based workflows.

The derived containers include:

  1. Stack
  2. Queue
  3. Priority Queue
  4. Deque (Double-ended Queue)

Let’s explore each derived container in detail, including their operations, practical examples, and explanations.

Stack

A stack is a Last In, First Out (LIFO) container. It allows insertion and deletion only at the top of the stack.

Supported Operations

OperationDescription
push(element)Inserts an element at the top of the stack.
pop()Removes the top element from the stack.
top()Returns the top element of the stack.
empty()Checks whether the stack is empty.
size()Returns the number of elements in the stack.

Code Example

				
					#include <iostream>
#include <stack>
using namespace std;

int main() {
    stack<int> s;

    // Push elements
    s.push(10);
    s.push(20);
    s.push(30);

    // Display the top element
    cout << "Top element: " << s.top() << endl;

    // Pop an element
    s.pop();
    cout << "After popping, top element: " << s.top() << endl;

    // Check size and empty status
    cout << "Stack size: " << s.size() << endl;
    cout << "Is stack empty? " << (s.empty() ? "Yes" : "No") << endl;

    return 0;
}

				
			
				
					// Output 
Top element: 30
After popping, top element: 20
Stack size: 2
Is stack empty? No

				
			

Queue

A queue is a First In, First Out (FIFO) container. Elements are inserted at the back and removed from the front.

Supported Operations

OperationDescription
push(element)Inserts an element at the back of the queue.
pop()Removes the element at the front of the queue.
front()Returns the element at the front of the queue.
back()Returns the element at the back of the queue.
empty()Checks whether the queue is empty.
size()Returns the number of elements in the queue.

Code Example

				
					#include <iostream>
#include <queue>
using namespace std;

int main() {
    queue<int> q;

    // Push elements
    q.push(10);
    q.push(20);
    q.push(30);

    // Display the front and back elements
    cout << "Front element: " << q.front() << endl;
    cout << "Back element: " << q.back() << endl;

    // Pop an element
    q.pop();
    cout << "After popping, front element: " << q.front() << endl;

    // Check size and empty status
    cout << "Queue size: " << q.size() << endl;
    cout << "Is queue empty? " << (q.empty() ? "Yes" : "No") << endl;

    return 0;
}

				
			
				
					// Output 
Front element: 10
Back element: 30
After popping, front element: 20
Queue size: 2
Is queue empty? No


				
			

Priority Queue

A priority_queue is a container where elements are arranged in descending order by default (max-heap). The highest-priority element is at the top.

Supported Operations

OperationDescription
push(element)Inserts an element and arranges it in order of priority.
pop()Removes the highest-priority element.
top()Returns the highest-priority element.
empty()Checks whether the priority queue is empty.
size()Returns the number of elements in the priority queue.

Code Example

				
					#include <iostream>
#include <queue>
using namespace std;

int main() {
    priority_queue<int> pq;

    // Push elements
    pq.push(10);
    pq.push(20);
    pq.push(15);

    // Display the top element
    cout << "Top element: " << pq.top() << endl;

    // Pop an element
    pq.pop();
    cout << "After popping, top element: " << pq.top() << endl;

    // Check size and empty status
    cout << "Priority queue size: " << pq.size() << endl;
    cout << "Is priority queue empty? " << (pq.empty() ? "Yes" : "No") << endl;

    return 0;
}

				
			
				
					// Output 
Top element: 20
After popping, top element: 15
Priority queue size: 2
Is priority queue empty? No


				
			

Deque (Double-Ended Queue)

A deque allows insertion and deletion at both ends.

Supported Operations

OperationDescription
push_front(element)Inserts an element at the front of the deque.
push_back(element)Inserts an element at the back of the deque.
pop_front()Removes the front element of the deque.
pop_back()Removes the back element of the deque.
front()Returns the front element of the deque.
back()Returns the back element of the deque.
empty()Checks whether the deque is empty.
size()Returns the number of elements in the deque.
clear()Removes all elements from the deque.
operator[]Accesses an element by index.

Code Example

				
					#include <iostream>
#include <deque>
using namespace std;

int main() {
    deque<int> d;

    // Push elements at both ends
    d.push_back(10);
    d.push_front(20);

    // Display the front and back elements
    cout << "Front element: " << d.front() << endl;
    cout << "Back element: " << d.back() << endl;

    // Pop elements from both ends
    d.pop_front();
    cout << "After popping front, front element: " << d.front() << endl;

    // Add more elements and access by index
    d.push_back(30);
    cout << "Element at index 0: " << d[0] << endl;

    return 0;
}

				
			
				
					// Output 
Front element: 20
Back element: 10
After popping front, front element: 10
Element at index 0: 10

				
			

STL containers provide a versatile and efficient way to manage collections of data in C++. By offering various types such as sequence containers, associative containers, and unordered containers, STL enables developers to choose the right tool for specific use cases. Their integration with STL algorithms and iterators ensures seamless data manipulation, making them indispensable for modern C++ programming. Happy coding !❤️

Table of Contents