Open-Source Internship opportunity by OpenGenus for programmers. Apply now.
Reading time: 20 minutes
The Standard Template Library (STL) is a set of C++ template classes to provide common programming data structures and functions such as lists, stacks, arrays, etc. It is a library of container classes, algorithms, and iterators. It is a generalized library and so, its components are parameterized. A working knowledge of template classes is a prerequisite for working with STL.
STL has four components :
- Algorithms
- Containers
- Functions
- Iterators
In this artile we will study about containers.
Containers or container classes store objects and data. There are in total seven standard “first-class” container classes and three container adaptor classes and only seven header files that provide access to these containers or container adaptors.
Sequence Containers
It implement data structures which can be accessed in a sequential manner.
vector : Vectors are same as dynamic arrays with the ability to resize itself automatically when an element is inserted or deleted, with their storage being handled automatically by the container.
Vector elements are placed in contiguous storage so that they can be accessed and traversed using iterators. In vectors, data is inserted at the end.
Inserting at the end takes differential time, as sometimes there may be a need of extending the array.
Removing the last element takes only constant time because no resizing happens. Inserting and erasing at the beginning or in the middle is linear in time.
// C++ program to illustrate the
// iterators in vector
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> g1;
for (int i = 1; i <= 5; i++)
g1.push_back(i);
cout << "Output of begin and end: ";
for (auto i = g1.begin(); i != g1.end(); ++i)
cout << *i << " ";
cout << "\nOutput of cbegin and cend: ";
for (auto i = g1.cbegin(); i != g1.cend(); ++i)
cout << *i << " ";
cout << "\nOutput of rbegin and rend: ";
for (auto ir = g1.rbegin(); ir != g1.rend(); ++ir)
cout << *ir << " ";
cout << "\nOutput of crbegin and crend : ";
for (auto ir = g1.crbegin(); ir != g1.crend(); ++ir)
cout << *ir << " ";
return 0;
}
Output :
Output of begin and end: 1 2 3 4 5
Output of cbegin and cend: 1 2 3 4 5
Output of rbegin and rend: 5 4 3 2 1
Output of crbegin and crend : 5 4 3 2 1
List : Lists are sequence containers that allow non-contiguous memory allocation. As compared to vector, list has slow traversal, but once a position has been found, insertion and deletion are quick.
Normally, when we say a List, we talk about doubly linked list.
For implementing a singly linked list, we use forward list.
#include <iostream>
#include <list>
#include <iterator>
using namespace std;
//function for printing the elements in a list
void showlist(list <int> g)
{
list <int> :: iterator it;
for(it = g.begin(); it != g.end(); ++it)
cout << '\t' << *it;
cout << '\n';
}
int main()
{
list <int> gqlist1, gqlist2;
for (int i = 0; i < 10; ++i)
{
gqlist1.push_back(i * 2);
gqlist2.push_front(i * 3);
}
cout << "\nList 1 (gqlist1) is : ";
showlist(gqlist1);
cout << "\nList 2 (gqlist2) is : ";
showlist(gqlist2);
cout << "\ngqlist1.front() : " << gqlist1.front();
cout << "\ngqlist1.back() : " << gqlist1.back();
cout << "\ngqlist1.pop_front() : ";
gqlist1.pop_front();
showlist(gqlist1);
cout << "\ngqlist2.pop_back() : ";
gqlist2.pop_back();
showlist(gqlist2);
cout << "\ngqlist1.reverse() : ";
gqlist1.reverse();
showlist(gqlist1);
cout << "\ngqlist2.sort(): ";
gqlist2.sort();
showlist(gqlist2);
return 0;
}
Output :
List 1 (gqlist1) is : 0 2 4 6
8 10 12 14 16 18
List 2 (gqlist2) is : 27 24 21 18
15 12 9 6 3 0
gqlist1.front() : 0
gqlist1.back() : 18
gqlist1.pop_front() : 2 4 6 8
10 12 14 16 18
gqlist2.pop_back() : 27 24 21 18
15 12 9 6 3
gqlist1.reverse() : 18 16 14 12
10 8 6 4 2
gqlist2.sort(): 3 6 9 12
15 18 21 24 27
Deque : double ended queues are sequence containers with the feature of expansion and contraction on both the ends.
They are similar to vectors, but are more efficient in case of insertion and deletion of elements. Unlike vectors, contiguous storage allocation may not be guaranteed.
Double Ended Queues are basically an implementation of the data structure double ended queue.
A queue data structure allows insertion only at the end and deletion from the front. This is like a queue in real life, wherein people are removed from the front and added at the back.
Double ended queues are a special case of queues where insertion and deletion operations are possible at both the ends.
The functions for deque are same as vector, with an addition of push and pop operations for both front and back.
#include <iostream>
#include <deque>
using namespace std;
void showdq(deque <int> g)
{
deque <int> :: iterator it;
for (it = g.begin(); it != g.end(); ++it)
cout << '\t' << *it;
cout << '\n';
}
int main()
{
deque <int> gquiz;
gquiz.push_back(10);
gquiz.push_front(20);
gquiz.push_back(30);
gquiz.push_front(15);
cout << "The deque gquiz is : ";
showdq(gquiz);
cout << "\ngquiz.size() : " << gquiz.size();
cout << "\ngquiz.max_size() : " << gquiz.max_size();
cout << "\ngquiz.at(2) : " << gquiz.at(2);
cout << "\ngquiz.front() : " << gquiz.front();
cout << "\ngquiz.back() : " << gquiz.back();
cout << "\ngquiz.pop_front() : ";
gquiz.pop_front();
showdq(gquiz);
cout << "\ngquiz.pop_back() : ";
gquiz.pop_back();
showdq(gquiz);
return 0;
}
Output :
The deque gquiz is : 15 20 10 30
gquiz.size() : 4
gquiz.max_size() : 4611686018427387903
gquiz.at(2) : 10
gquiz.front() : 15
gquiz.back() : 30
gquiz.pop_front() : 20 10 30
gquiz.pop_back() : 20 10
Array : The introduction of array class from C++11 has offered a better alternative for C-style arrays. The advantages of array class over C-style array are :-
- Array classes knows its own size, whereas C-style arrays lack this property. So when passing to functions, we don’t need to pass size of Array as a separate parameter.
- With C-style array there is more risk of array being decayed into a pointer. Array classes don’t decay into pointers
- Array classes are generally more efficient, light-weight and reliable than C-style arrays.
// C++ code to demonstrate working of array,
// to() and get()
#include<iostream>
#include<array> // for array, at()
#include<tuple> // for get()
using namespace std;
int main()
{
// Initializing the array elements
array<int,6> ar = {1, 2, 3, 4, 5, 6};
// Printing array elements using at()
cout << "The array elemets are (using at()) : ";
for ( int i=0; i<6; i++)
cout << ar.at(i) << " ";
cout << endl;
// Printing array elements using get()
cout << "The array elemets are (using get()) : ";
cout << get<0>(ar) << " " << get<1>(ar) << " ";
cout << get<2>(ar) << " " << get<3>(ar) << " ";
cout << get<4>(ar) << " " << get<5>(ar) << " ";
cout << endl;
// Printing array elements using operator[]
cout << "The array elements are (using operator[]) : ";
for ( int i=0; i<6; i++)
cout << ar[i] << " ";
cout << endl;
return 0;
}
Output :
The array elemets are (using at()) : 1 2 3 4 5 6
The array elemets are (using get()) : 1 2 3 4 5 6
The array elements are (using operator[]) : 1 2 3 4 5 6
Container Adapter
It provide a different interface for sequential containers.
Queue : Queues are a type of container adaptors which operate in a first in first out (FIFO) type of arrangement. Elements are inserted at the back (end) and are deleted from the front.
// CPP code to illustrate
// Queue in Standard Template Library (STL)
#include <iostream>
#include <queue>
using namespace std;
void showq(queue <int> gq)
{
queue <int> g = gq;
while (!g.empty())
{
cout << '\t' << g.front();
g.pop();
}
cout << '\n';
}
int main()
{
queue <int> gquiz;
gquiz.push(10);
gquiz.push(20);
gquiz.push(30);
cout << "The queue gquiz is : ";
showq(gquiz);
cout << "\ngquiz.size() : " << gquiz.size();
cout << "\ngquiz.front() : " << gquiz.front();
cout << "\ngquiz.back() : " << gquiz.back();
cout << "\ngquiz.pop() : ";
gquiz.pop();
showq(gquiz);
return 0;
}
Output :
The queue gquiz is : 10 20 30
gquiz.size() : 3
gquiz.front() : 10
gquiz.back() : 30
gquiz.pop() : 20 30
Priority Queue : Priority queues are a type of container adapters, specifically designed such that the first element of the queue is the greatest of all elements in the queue and elements are in non decreasing order(hence we can see that each element of the queue has a priority{fixed order}).
#include <iostream>
#include <queue>
using namespace std;
void showpq(priority_queue <int> gq)
{
priority_queue <int> g = gq;
while (!g.empty())
{
cout << '\t' << g.top();
g.pop();
}
cout << '\n';
}
int main ()
{
priority_queue <int> gquiz;
gquiz.push(10);
gquiz.push(30);
gquiz.push(20);
gquiz.push(5);
gquiz.push(1);
cout << "The priority queue gquiz is : ";
showpq(gquiz);
cout << "\ngquiz.size() : " << gquiz.size();
cout << "\ngquiz.top() : " << gquiz.top();
cout << "\ngquiz.pop() : ";
gquiz.pop();
showpq(gquiz);
return 0;
}
Output :
The priority queue gquiz is : 30 20 10 5 1
gquiz.size() : 5
gquiz.top() : 30
gquiz.pop() : 20 10 5 1
Stack : Stacks are a type of container adaptors with LIFO(Last In First Out) type of working, where a new element is added at one end and (top) an element is removed from that end only.
// CPP program to demonstrate working of STL stack
#include <iostream>
#include <stack>
using namespace std;
void showstack(stack <int> s)
{
while (!s.empty())
{
cout << '\t' << s.top();
s.pop();
}
cout << '\n';
}
int main ()
{
stack <int> s;
s.push(10);
s.push(30);
s.push(20);
s.push(5);
s.push(1);
cout << "The stack is : ";
showstack(s);
cout << "\ns.size() : " << s.size();
cout << "\ns.top() : " << s.top();
cout << "\ns.pop() : ";
s.pop();
showstack(s);
return 0;
}
Output :
The stack is : 1 5 20 30 10
s.size() : 5
s.top() : 1
s.pop() : 5 20 30 10
Associative Container
It implement sorted data structures that can be quickly searched (O(log n) complexity).
Set : Sets are a type of associative containers in which each element has to be unique, because the value of the element identifies it.
The value of the element cannot be modified once it is added to the set, though it is possible to remove and add the modified value of that element.
#include <iostream>
#include <set>
#include <iterator>
using namespace std;
int main()
{
// empty set container
set <int, greater <int> > gquiz1;
// insert elements in random order
gquiz1.insert(40);
gquiz1.insert(30);
gquiz1.insert(60);
gquiz1.insert(20);
gquiz1.insert(50);
gquiz1.insert(50); // only one 50 will be added to the set
gquiz1.insert(10);
// printing set gquiz1
set <int, greater <int> > :: iterator itr;
cout << "\nThe set gquiz1 is : ";
for (itr = gquiz1.begin(); itr != gquiz1.end(); ++itr)
{
cout << '\t' << *itr;
}
cout << endl;
// assigning the elements from gquiz1 to gquiz2
set <int> gquiz2(gquiz1.begin(), gquiz1.end());
// print all elements of the set gquiz2
cout << "\nThe set gquiz2 after assign from gquiz1 is : ";
for (itr = gquiz2.begin(); itr != gquiz2.end(); ++itr)
{
cout << '\t' << *itr;
}
cout << endl;
// remove all elements up to 30 in gquiz2
cout << "\ngquiz2 after removal of elements less than 30 : ";
gquiz2.erase(gquiz2.begin(), gquiz2.find(30));
for (itr = gquiz2.begin(); itr != gquiz2.end(); ++itr)
{
cout << '\t' << *itr;
}
// remove element with value 50 in gquiz2
int num;
num = gquiz2.erase (50);
cout << "\ngquiz2.erase(50) : ";
cout << num << " removed \t" ;
for (itr = gquiz2.begin(); itr != gquiz2.end(); ++itr)
{
cout << '\t' << *itr;
}
cout << endl;
//lower bound and upper bound for set gquiz1
cout << "gquiz1.lower_bound(40) : "
<< *gquiz1.lower_bound(40) << endl;
cout << "gquiz1.upper_bound(40) : "
<< *gquiz1.upper_bound(40) << endl;
//lower bound and upper bound for set gquiz2
cout << "gquiz2.lower_bound(40) : "
<< *gquiz2.lower_bound(40) << endl;
cout << "gquiz2.upper_bound(40) : "
<< *gquiz2.upper_bound(40) << endl;
return 0;
}
Output :
The set gquiz1 is : 60 50 40 30 20 10
The set gquiz2 after assign from gquiz1 is : 10 20 30 40 50 60
gquiz2 after removal of elements less than 30 : 30 40 50 60
gquiz2.erase(50) : 1 removed 30 40 60
gquiz1.lower_bound(40) : 40
gquiz1.upper_bound(40) : 30
gquiz2.lower_bound(40) : 40
gquiz2.upper_bound(40) : 60
Maps : Maps are associative containers that store elements in a mapped fashion. Each element has a key value and a mapped value. No two mapped values can have same key values.
#include <iostream>
#include <iterator>
#include <map>
using namespace std;
int main()
{
// empty map container
map<int, int> gquiz1;
// insert elements in random order
gquiz1.insert(pair<int, int>(1, 40));
gquiz1.insert(pair<int, int>(2, 30));
gquiz1.insert(pair<int, int>(3, 60));
gquiz1.insert(pair<int, int>(4, 20));
gquiz1.insert(pair<int, int>(5, 50));
gquiz1.insert(pair<int, int>(6, 50));
gquiz1.insert(pair<int, int>(7, 10));
// printing map gquiz1
map<int, int>::iterator itr;
cout << "\nThe map gquiz1 is : \n";
cout << "\tKEY\tELEMENT\n";
for (itr = gquiz1.begin(); itr != gquiz1.end(); ++itr) {
cout << '\t' << itr->first
<< '\t' << itr->second << '\n';
}
cout << endl;
// assigning the elements from gquiz1 to gquiz2
map<int, int> gquiz2(gquiz1.begin(), gquiz1.end());
// print all elements of the map gquiz2
cout << "\nThe map gquiz2 after"
<< " assign from gquiz1 is : \n";
cout << "\tKEY\tELEMENT\n";
for (itr = gquiz2.begin(); itr != gquiz2.end(); ++itr) {
cout << '\t' << itr->first
<< '\t' << itr->second << '\n';
}
cout << endl;
// remove all elements up to
// element with key=3 in gquiz2
cout << "\ngquiz2 after removal of"
" elements less than key=3 : \n";
cout << "\tKEY\tELEMENT\n";
gquiz2.erase(gquiz2.begin(), gquiz2.find(3));
for (itr = gquiz2.begin(); itr != gquiz2.end(); ++itr) {
cout << '\t' << itr->first
<< '\t' << itr->second << '\n';
}
// remove all elements with key = 4
int num;
num = gquiz2.erase(4);
cout << "\ngquiz2.erase(4) : ";
cout << num << " removed \n";
cout << "\tKEY\tELEMENT\n";
for (itr = gquiz2.begin(); itr != gquiz2.end(); ++itr) {
cout << '\t' << itr->first
<< '\t' << itr->second << '\n';
}
cout << endl;
// lower bound and upper bound for map gquiz1 key = 5
cout << "gquiz1.lower_bound(5) : "
<< "\tKEY = ";
cout << gquiz1.lower_bound(5)->first << '\t';
cout << "\tELEMENT = "
<< gquiz1.lower_bound(5)->second << endl;
cout << "gquiz1.upper_bound(5) : "
<< "\tKEY = ";
cout << gquiz1.upper_bound(5)->first << '\t';
cout << "\tELEMENT = "
<< gquiz1.upper_bound(5)->second << endl;
return 0;
}
Output:
The map gquiz1 is :
KEY ELEMENT
1 40
2 30
3 60
4 20
5 50
6 50
7 10
The map gquiz2 after assign from gquiz1 is :
KEY ELEMENT
1 40
2 30
3 60
4 20
5 50
6 50
7 10
gquiz2 after removal of elements less than key=3 :
KEY ELEMENT
3 60
4 20
5 50
6 50
7 10
gquiz2.erase(4) : 1 removed
KEY ELEMENT
3 60
5 50
6 50
7 10
gquiz1.lower_bound(5) : KEY = 5 ELEMENT = 50
gquiz1.upper_bound(5) : KEY = 6 ELEMENT = 50
Unordered Associative Containers
It implement unordered data structures that can be quickly searched.
unordered_set : It is implemented using hash table where keys are hashed into indices of this hash table so it is not possible to maintain an order.
All operation on unordered_set takes constant time O(1) on an average which can go up to linear time in worst case which depends on the internally used hash function but practically they perform very well and generally provide constant time lookup operation.
The unordered-set can contain key of any type – predefined or user-defined data structure but when we define key of type user define type, we need to specify our comparison function according to which keys will be compared.
// C++ program to demonstrate various function of unordered_set
#include <bits/stdc++.h>
using namespace std;
int main()
{
// declaring set for storing string data-type
unordered_set<string> stringSet;
// inserting various string, same string will be stored
// once in set
stringSet.insert("code");
stringSet.insert("in");
stringSet.insert("c++");
stringSet.insert("is");
stringSet.insert("fast");
string key = "slow";
// find returns end iterator if key is not found,
// else it returns iterator to that key
if (stringSet.find(key) == stringSet.end())
cout << key << " not found\n\n";
else
cout << "Found " << key << endl << endl;
key = "c++";
if (stringSet.find(key) == stringSet.end())
cout << key << " not found\n";
else
cout << "Found " << key << endl;
// now iterating over whole set and printing its
// content
cout << "\nAll elements : ";
unordered_set<string> :: iterator itr;
for (itr = stringSet.begin(); itr != stringSet.end(); itr++)
cout << (*itr) << endl;
}
Output :
slow not found
Found c++
All elements :
is
fast
c++
in
code
unordered_map : unordered_map is an associated container that stores elements formed by combination of key value and a mapped value.
The key value is used to uniquely identify the element and mapped value is the content associated with the key. Both key and value can be of any type predefined or user-defined.
Internally unordered_map is implemented using Hash Table, the key provided to map are hashed into indices of hash table that is why performance of data structure depends on hash function a lot but on an average the cost of search, insert and delete from hash table is O(1).
// C++ program to demonstrate functionality of unordered_map
#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
// Declaring umap to be of <string, int> type
// key will be of string type and mapped value will
// be of double type
unordered_map<string, int> umap;
// inserting values by using [] operator
umap["Practice"] = 20;
umap["Contribute"] = 30;
// Traversing an unordered map
for (auto x : umap)
cout << x.first << " " << x.second << endl;
}
Output :
Contribute 30
Practice 20