A container is an object that stores a collection of elements (other objects). Each container manages the storage space for its elements and provides access to them through iterators and/or member functions.
Containers are implemented as classes in STL and thus provide several member functions.
Note : iterators are used to point to the containers in STL, so iterators are somewhat like pointers in C/C++.
There are three categories of containers in STL :
1. sequence containers (eg. vector, list etc.)
2. associative containers (eg. map, set etc. )
3. unordered containers (eg. unordered_set, unordered_map etc.)
To use containers or algorithms in STL, we need to use the corresponding
header files, like
#include<vector> // for using vector container
#include<deque>
#include<list>
#include<set>
#include<map>
Their use can be easily understood by looking at their names
If
you want to avoid writing separate header files, you can use a single header
file to encapsulate all the functionality provided by STL.That header is
#include<bits/stdc++.h>
All the STL functionality is implemented inside 'std' namespace. 'std'
is abbreviation for standard. We need to use 'std' namespace for using any STL
container or algorithm. Therefore I recommend importing 'std' namespace in
your program to avoid preceding everything with 'std::'.
so we add
:
using namespace std;
Now we are ready to start writing STL code.
Let's discuss sequence containers.
Sequence Containers :
They are used to implement data structures that are sequential in nature like
arrays(array) and linked list(list).
Some sequence containers are :
vector, deque, list, array etc.
Let's discuss some of them in detail.
1. vector
A vector is a dynamic array ( size not fixed) which grows in one
direction(towards the end). It stores elements in contiguous memory
locations.
=> Creating a vector :
vector provides different constructors for its creation.
For example to
create an integer vector vec, we write
a.
vector<int> vec; // creates an
empty vector. vec.size() = 0
//
vec.size() is a member function provided by vector which tells the current
number
of elements in vector.
b.
vector<int> vec(10); // creates a vector of 10 uninitialized integers.
vec.size()=10;
c.
vector<int> vec(10,0);
// creates a vector of 10 integers initilaized to 0.
vec.size()=10;
d.
vector<int> vec(vec2); // copy constructor copies all values
of vec2 into vec
e.
vector<int> vec = {2,3,5};
=> Adding/removing an element at end of vector
Since vector is dynamic, you can grow or shrink your vector from the end as
per your requirement.
vector<int> vec; // vec.size()=0
vec.push_back(10); // inserts 10 at the end of
vector vec={10}
vec.push_back(20); // vec={10,20}
vec.pop_back(); // removes last
element(20) from vector vec={10};
=> Access an element of vector
vector provides random access just like array.
a.
cout<<vec[1];
// prints 2nd element of vec(0 base indexing)
but performs no range check
b.
cout<<vec.at(1);
// also performs range check and throws exception if index out of range of
vec
Note : 'auto' is introduced from C++ 11 and is
useful for difficult type names. The compiler automatically infers the correct
type from the data provided.
=> Traversing all the vector elements using for loop.
a. traversal using index of elements
for(int i=0;i<vec.size();i++)
cout<<vec[i]<<" ";
b. using for-each version
for(auto v : vec)
// v automatically gets assigned values of vector one by
one but it is read only traversal
cout<<v<<" ";
c. using iterator
for(auto it=vec.begin();it!=vec.end();it++) // auto can be replaced by
vector<int> :: iterator
cout<<*it<<" ";
=> Traversal using iterator is recommended because :
1.
This methods is faster than using random access operator.
2. This
a universal way of traversing any container in STL and therefore very
important.
Note : These are some common member
functions for all STL containers :
1.
vec.empty()
// boolean function, returns true(0) if vector has no
elements, false otherwise.
2. vec.size() // returns current size of vector
3. vec.clear()
// removes all the elements of vector
Properties of vector :
a. Fast insertion/removal at the end. Time complexity O(1).
b. Slow insertion/removal at any place other than the end
because vector is stored in contiguous memory locations so the other elements
need to be shifted to make space for new insertion. Time complexity
O(n) (linear).
c. Slow search of elements as there is
no order among them. Time complexity O(n).
2. deque
deque is very similar to vector. The main difference is that vector grows in
only one direction, whereas deque grows in both the directions, i.e
from front as well as end.
=> Creating a deque is similar to creating a vector
deque<int> dq;
=> Adding/removing an element at beginning or at end of vector
push_back(val), push_front(val) // add new element at the end or front of vector
respectively.
pop_back(), pop_front() // remove an element from
the end or front of vector respectively.
=> Traversing deque is similar to vector as deque also provides random
access operator like vector.
Properties of deque:
a. Fast insertion/removal at beginning or at end. Time complexity
O(1).
b. Slow insertion/removal at any other place.
Time complexity O(n) (linear).
c. Slow search of
elements as there is no order among them. Time complexity O(n).
3. list
A list in STL is a doubly linked list data structure in which
each node has two pointers pointing to to its next node and previous node
respectively.
The key feature of list is that we have fast insert/removal
of any element of the list at any position but only if we have an iterator to
the desired position.
=> Creating a list
list<int> lis ={2,20,10};
=> list also provides methods push_back(),push_front(), pop_back(),
pop_front() provided by deque for insertion/removal at back or front
respectively.
To insert an element at any other position, first we
require iterator to the desired position.
Let's insert value 5 at
position of 20.
we use find algorithm function(which we will
discuss later) to get an iterator to position where 20 is sotred.
auto it = find(lis.begin(),lis.end(),20); // it -> 20 it is of type list::iterator but we
use auto
To insert new value 5 at position pointed by iterator,, we
use insert member function
lis.insert(it,5);
// Complexity O(1). lis={2,5,20,10};
We
can increment or decrement iterator to move to next or previous element in
list respectively.
it++;
// it -> 20
To erase an
element pointed by iterator(20) use.
lis.erase(it);
// Complexity O(1), lis={2,5,10}
Properties of list :
a. Fast insertion/removal
at any place in list. Time complexity O(1).
b. No
random access operator like vector/deque. So traversal is only possible using
iterators.
c. Slow search of elements. Time complexity
O(n). But this searching is comparitively slower than vector because of
locality of individual elements. In vector, elements
are stored in contiguous memory locations, and cache memory gives more cache
hits for contagious elements. On the other hand, list elements are stored
randomly in memory so it gives more cache misses and we keep swapping elements
in and out of cache.
d. Each node in list stores two extra
pointers one pointing to next element and one pointing to previous element.
Thus list occupies more memory and this also leads to higher cache
misses.
These were some of the mostly used sequence containers in STL. This
completes our discussion of Sequence containers. If you have any doubts.
please mention in the comments section.
Also check out other types of C++ STL containers if you missed them out :
1. Associative Containers
2. Unordered Containers
Thank you for your patience reading. If you enjoyed this post, I’d be very
grateful if you’d help it spread by emailing it to a friend, or sharing it on
Whatsapp or Facebook.
😇Happy Learning!!