C++ Containers

STL containers for storing and managing data collections

📦 What are Containers?

Containers are STL classes that store collections of objects. They provide different ways to organize data with built-in functions for insertion, deletion, and access operations.


#include <vector>
using namespace std;

vector<int> numbers = {10, 20, 30};
numbers.push_back(40);
cout << "Size: " << numbers.size();
                                    

Output:

Size: 4

Container Categories

📋

Sequence Containers

Sequence containers store elements in a strict linear order, allowing positional access. C++ offers std::vector for dynamic arrays, std::list for doubly-linked lists, and std::deque for double-ended queues. Each has distinct performance traits: vectors enable fast random access, lists excel at insertions/deletions anywhere, and deques allow efficient front/back operations. Choosing the right container depends on your access patterns and modification needs.

vector list deque array
🗂️

Associative Containers

Sorted key-based storage

set map multiset multimap
🔧

Container Adapters

Container adapters provide specialized interfaces for common data structures like stacks, queues, and priority queues. They wrap underlying sequence containers (e.g., std::deque or std::vector) to enforce specific access rules. A stack (std::stack) follows LIFO (Last-In, First-Out), a queue (std::queue) follows FIFO (First-In, First-Out), and a priority queue (std::priority_queue) orders elements by priority. They simplify implementation of standard data patterns.

stack queue priority_queue

Unordered Containers

Hash-based fast access

unordered_set unordered_map

🔹 Sequence Containers

Sequence containers store elements in a strict linear order, allowing positional access. C++ offers std::vector for dynamic arrays, std::list for doubly-linked lists, and std::deque for double-ended queues. Each has distinct performance traits: vectors enable fast random access, lists excel at insertions/deletions anywhere, and deques allow efficient front/back operations. Choosing the right container depends on your access patterns and modification needs.

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

int main() {
    // Vector - dynamic array
    vector<int> vec = {1, 2, 3};
    vec.push_back(4);
    cout << "Vector[0]: " << vec[0] << endl;
    
    // List - doubly linked list
    list<string> lst = {"apple", "banana"};
    lst.push_front("orange");
    cout << "List size: " << lst.size() << endl;
    
    // Deque - double-ended queue
    deque<int> dq = {10, 20};
    dq.push_front(5);
    dq.push_back(30);
    cout << "Deque front: " << dq.front() << endl;
    
    return 0;
}

Output:

Vector[0]: 1
List size: 3
Deque front: 5

🔹 Container Adapters

Container adapters provide specialized interfaces for common data structures like stacks, queues, and priority queues. They wrap underlying sequence containers (e.g., std::deque or std::vector) to enforce specific access rules. A stack (std::stack) follows LIFO (Last-In, First-Out), a queue (std::queue) follows FIFO (First-In, First-Out), and a priority queue (std::priority_queue) orders elements by priority. They simplify implementation of standard data patterns.

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

int main() {
    // Stack - LIFO (Last In, First Out)
    stack<int> stk;
    stk.push(10);
    stk.push(20);
    stk.push(30);
    
    cout << "Stack top: " << stk.top() << endl;
    stk.pop();
    cout << "After pop: " << stk.top() << endl;
    
    // Queue - FIFO (First In, First Out)
    queue<string> q;
    q.push("first");
    q.push("second");
    q.push("third");
    
    cout << "Queue front: " << q.front() << endl;
    q.pop();
    cout << "After pop: " << q.front() << endl;
    
    return 0;
}

Output:

Stack top: 30
After pop: 20
Queue front: first
After pop: second

🔹 Common Container Operations

Most C++ containers share a set of common member functions for basic management. These include size() to get element count, empty() to check if the container is empty, front() and back() to access ends, and clear() to remove all elements. Iterators like begin() and end() enable range traversal. Understanding these universal operations ensures consistent and portable code across different container types.

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

int main() {
    vector<int> container = {10, 20, 30};
    
    // Size operations
    cout << "Size: " << container.size() << endl;
    cout << "Empty: " << (container.empty() ? "Yes" : "No") << endl;
    
    // Access operations
    cout << "First element: " << container.front() << endl;
    cout << "Last element: " << container.back() << endl;
    
    // Modification operations
    container.push_back(40);
    container.pop_back();
    
    // Iteration
    cout << "Elements: ";
    for(auto element : container) {
        cout << element << " ";
    }
    cout << endl;
    
    // Clear all elements
    container.clear();
    cout << "After clear, size: " << container.size() << endl;
    
    return 0;
}

Output:

Size: 3
Empty: No
First element: 10
Last element: 30
Elements: 10 20 30
After clear, size: 0

🔹 Choosing the Right Container

Select containers based on your specific needs:

When to use each container:

  • vector: Random access, dynamic size, frequent back insertions
  • list: Frequent insertions/deletions in middle, no random access needed
  • deque: Insertions/deletions at both ends
  • stack: LIFO operations (undo functionality, expression evaluation)
  • queue: FIFO operations (task scheduling, breadth-first search)

🧠 Test Your Knowledge

Which container provides LIFO (Last In, First Out) access?