STL Container Class Introduction by consulttoday


STL Container Class Introduction

Before we start dig deep into STL, it is mandatory that we learn about Container classes.

A Container class is defined as a class that gives you the power to store any type of data. There are two type of container classes available in STL in C++, namely “Simple Container Classes” and “Associative Container Classes”. An Associative Container class associates a key to each object to minimize the average access time.

 

Simple Container Classes

  • vector<>
  • lists<>
  • stack<>
  • queue<>
  • deque<>

Associative Container Classes

  • map<>
  • set<>
  • multimap<>
  • multiset<>

This article will discuss about the Simple Container Classes in detail. This will show you how to use the different methods of these classes to achieve your goal. Let’s Start with vector<>

vector<>   The Open Array

Constructor of Vector class

There are 3 overloaded constructors in the vector class excluding the default one. We will discuss about them one by one. You can create a vector object with predefined size and values for them. Here is how you can do that. Suppose you want to create a vector<int> object whose initial capacity will be 10 and all the values will be set to 2. Then you can create such an object using one of the overloaded versions of the vector constructor

vector<int> nums(10,2);

If you just want to specify the capacity but don’t want to assign values for them then you can use the other constructor which accepts an integer to set the capacity of the vector like

vector<int> nums(100);

The last constructor allows you to initialize the vector with data from other vectors or arrays or other collections. Suppose you want to copy one vector to the other then this constructor proves to be very handy. Here is the code snippet.

vector<int> nums; for(int i=1;i<5;i++) nums.push_back(i); vector<int> codes(nums);

Here codes is a vector. We have copied the contents of nums in codes using the constructor.

Methods of vector class

 

  • _Destroy()
  • _Eq()
  • _Lt()
  • _Ucopy()
  • _Ufill()
  • assign()
  • at()
  • begin()
  • back()
  • capacity()
  • clear()
  • empty()
  • end()
  • erase()
  • front()
  • get_allocator()
  • max_size()
  • insert()
  • operator=
  • operator[]
  • pop_back()
  • push_back()
  • rbegin()
  • rend()
  • reserve()
  • resize()
  • size()
  • swap()
  • ~vector()

Here we will discuss about each method, will show you what it does and then at the end we will give example where you can use these methods together….

The methods that start with an underscore are protected methods and can’t be accessed using object of the class. These methods are used by other method internally.

assign() and size()


This method is used to assign an initial size to the vector. This method has 2 overloaded versions. Here we have used the first overloaded method. This method takes an integer argument to initialize the size of the vector.

#include <iostream>
#include <vector>
#include <conio.h>

using namespace std;

int main()
{
vector<char> codes;
codes.assign(10);//Assigning the size to 10.
cout<<codes.size();//prints the size of the vector
getch();
return 0;
}
 

The next overloaded match takes 2 pointer as arguments. Here is a sample code

#include <iostream>
#include <vector>
#include <conio.h>
using namespace std;

int main()
{
char a[3]={‘a’,'b’,'c’};
vector<char> orders;
orders .assign(a,a+3);
cout<<orders.size();
getch();
return 0;
}
 

Here the pointer used are nothing but the array name. This concept owes to C. Basically array name is a pointer to that array.

at()


No matter how sophisticated tool we get, accessing the old C style array using the index is really popular. This thing was kept in mind when STL was designed. As vector behaves like an open array, people expect it to show other behaviors of the array. at() is a method that takes an integer as argument and return the value at that location. So in a way it simulates the age-old array indexing. Here is a code snippet.

#include <iostream>
#include <vector>
#include <conio.h>

using namespace std;

int main()
{
vector<int> codes;
for(int i=0;i<10;i++)
codes.push_back(i);
cout<<codes.at(9)<<endl;
getch();

return 0;
}
 

The output of this code will be 9.

begin() , end()


In STL within containers the elements are accessed using iterators. Iterators are something like pointers. begin() and end() returns the iterator to the beginning and to the end of the container. Here one thing should be noted end() points to a location which is one after the physical end of the container. These two methods are the most used ones in STL, because to traverse a container, you need to create an iterator. And then to initialize it’s value to the beginning of the container. Here is the code to traverse a vector<>. This code make use of begin() and end() methods.

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

int main()
{
vector<int> numbers;
for(int i=0;i<10;i++)
numbers.push_back(i);
//Assiging to the beginning
vector<int>::iterator k = numbers.begin();
//please note that k is kept less than
//numbers.end() because end() points to somewhere which is beyond physical end.
for(;k<numbers.end();k++)
cout<<*k<<endl;
return 0;
}
 

 

front(), back()


front() method returns the element at the front of the vector. back() method returns the element at the back of the vector. Here is the code to understand its operations better.

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

int main()
{
vector<int> m;
for(int i=0;i<10;i++)
m.push_back(i);
cout<<m.front()<<endl;
cout<<m.back()<<endl;
return 0;
}
 

The output of this program is
0
9
 

capacity() , size()


capacity() returns the number of elements the vector can hold initially assigned. For a vector although it is not very important method. On the other hand the method size() returns the number of elements currently in the vector. Here is the code that will help you understand the difference between capacity() and size().

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

int main()
{
vector<int> m(100);
cout<<m.capacity()<<endl;
for(int i=0;i<10;i++)
m.push_back(i);
cout<<m.size()<<endl;
return 0;
}
 

The output of the program is

100 110

Because initially using the constructor we created a vector whose capacity is 100. Then we are pushing 10 elements to its back. Therefore the size increases to 110.

clear() , erase()


As the name suggests clear() method clears the vector elements.

So the size of the vector becomes zero after the clear(). On the other hand erase helps to erase a particular element from the vector or a range of vectors. erase() has 2 overloaded versions for this purposes. One overloaded method takes one iterator the other one takes 2 iterators as starting and finishing deleting locations. Here is a code to illustrate

#include <iostream>
#include <vector>
using namespace std;
void Display(vector<int> m)
{
for(int i=0;i<m.size();i++)
cout<<m.at(i)<<endl;
}
int main()
{
vector<int> m;
for(int i=0;i<5;i++)
m.push_back(i);
cout<<”The full list is “<<endl;
Display(m);
cout<<”After deleting the first element “<<endl;
m.erase(m.begin());//Deleting the first element
Display(m);
cout<<”After deleting the first 2 elements”<<endl;
//Deleting first 2 elements
m.erase(m.begin(),m.begin()+2);
Display(m);
return 0;
}
 

The output of this code is

The full list is
0
1
2
3
4
After deleting the first element
1
2
3
4
After deleting the first 2 elements
3
4
 

empty()


This method checks whether the vector is empty or not. This returns a bool value if the vector size is 0, i.e., the vector is empty. Here is the code using empty()

#include <iostream>
#include <vector>

using namespace std;

voidDisplay(vector<int> m)
{
for(int i=0;i<m.size();i++)
cout<<m.at(i)<<endl;
}
int main()
{
vector<int> m;
for(int i=0;i<5;i++)
m.push_back(i);
cout<<”The full list is “<<endl;
if(!m.empty())
Display(m);
else
System.out.println(“The vector is empty”);
return 0;
}
 

swap()


This method swaps two vectors. Here is the code

#include <iostream>
#include <vector>

using namespace std;
voidDisplay(vector<int> m)
{
for(int i=0;i<m.size();i++)
cout<<m.at(i)<<endl;
}
int main()
{
vector<int> m;
vector<int> n(5,2);
for(int i=0;i<5;i++)
m.push_back(i);
cout<<”m :”<<endl;
Display(m);
cout<<”n :”<<endl;
Display(n);
m.swap(n);
cout<<”m :”<<endl;
Display(m);
cout<<”n :”<<endl;
Display(n);
return 0;
}
 

Here we have two vectors initially. Vector m takes the values 0 to 4 while the vector n takes the values 2. Here is the output of the code.

m :
0
1
2
3
4
n :
2
2
2
2
2
m :
2
2
2
2
2
n :
0
1
2
3
4
 

As you can see values of m and n are swapped.

max_size()


vectors can grow and diminish in runtime, but they also have a maximum size. This method returns the maximum size possible for a vector. As you may have already guessed, these values are different from type to type. But this value is same for a particular type. Suppose you want to declare a vector of int and another of int pointers. Both will have the same maximum size.

Here is a code that prints some of the max sizes.

#include <iostream>
#include <vector>

using namespace std;
int main()
{
vector<int> iv;
vector<float> fv;
vector<double> dv;
vector<char> cv;
vector<string> sv;
vector<bool> bv;

cout<<”Integer Vector Max Size :”<<iv.max_size()<<endl;
cout<<”Float Vector Max Size :”<<fv.max_size()<<endl;
cout<<”Double Vector Max Size :”<<dv.max_size()<<endl;
cout<<”Character Vector Max Size :”<<cv.max_size()<<endl;
cout<<”String Vector Max Size :”<<sv.max_size()<<endl;
cout<<”Boolean Vector Max Size :”<<bv.max_size()<<endl;

return 0;
}
 

Here is the output of the code.

Integer Vector Max Size :1073741823
Float Vector Max Size :1073741823
Double Vector Max Size :536870911
Character Vector Max Size :4294967295
String Vector Max Size :268435455
Boolean Vector Max Size :4294967295
 

reserve()


This method will reserve the values already there in the vector and then increase the capacity of the vector. Here is the code.

#include <iostream>
#include <vector>

using namespace std;

voidDisplay(vector<int> m)
{
for(int i=0;i<m.capacity();i++)
cout<<m.at(i)<<endl;
}

int main()
{
vector<int> m(4,3);
cout<<m.capacity()<<endl;
m.reserve(10);
cout<<m.capacity()<<endl;
Display(m);
return 0;
}
 

Here previously the capacity of the vector was 4. Then the vector size is increased but the values previously there in the vector will not be erased.

resize()


resize(), as the name suggests the method resizes the vector . If the argument given is greater than the size, then the capacity will be increased. We can also put a value to fill the remaining space.

#include <iostream>
#include <vector>

using namespace std;

voidDisplay(vector<int> m)
{
for(int i=0;i<m.capacity();i++)
cout<<m.at(i)<<endl;
}

int main()
{
vector<int> m(4,3);
cout<<m.capacity()<<endl;
m.resize(8,3);
cout<<m.capacity()<<endl;
Display(m);
return 0;
}
 

Here is the output of the program

4
8
3
3
3
3
3
3
3
3
 

As you can see the capacity of the vector is increased to 8.

rbegin(), rend()


rbegin() returns the iterator to the first element if the vector is reversed. Say there is a vector containing 1,4,5,6 then rbegin () returns an iterator for 6. On the other hand rend() returns the iterator to a location beyond the physical end when the vector is reversed.

In a nutshell, rbegin() and rend() are nothing but the reverse version of begin() and end(). Here is a code to illustrate their usages.

#include <iostream>
#include <vector>

using namespace std;

int main()
{
vector<int> m;
m.push_back(11);
m.push_back(12);
m.push_back(23);
m.push_back(25);
cout<<*m.rbegin()<<endl;
cout<<*(m.rend()-1)<<endl;
return 0;
}
 

The output of this code is

25
11

insert()


This method allows inserting any value wherever you please.

This has 3 overloaded versions. The first one accepts only 2 arguments. One is the iterator which indicates where the value is to be inserted and the other argument is the value itself.

 

Here is a code that uses this version of insert() method.

#include <iostream>
#include <vector>

using namespace std;

voidDisplay(vector<int> m)
{
for(int i=0;i<m.capacity();i++)
cout<<m.at(i)<<endl;
}

int main()
{
vector<int> nums;
for(int i=1;i<11;i++)
nums.push_back(i);
cout<<”Before inserting the 100 at 2nd place the vector was :”<<endl;
Display(nums);
nums.insert(nums.begin()+1,100);
cout<<”After inserting the 100 at 2nd place the vector is :”<<endl;
Display(nums);
return 0;
}
 

Here is the output of the above program

Before inserting the 100 at 2nd place the vector was :
1
2
3
4
5
6
7
8
9
10
After inserting the 100 at 2nd place the vector was :
1
100
2
3
4
5
6
7
8
9
10
 

There are two more overloaded versions for this method insert() Now we will discuss about the second one. It takes 3 arguments. This is used for copying part or full of another collection. (May be a vector, a plain C++ array or some other collections that is accessed using pointers/iterators) Say we have a plain C++ array like this

 

int array[] = {5,6,3,4};

And we want to insert 5,6,3 in our vector defined in the last program. We want to insert these values right there where we inserted 100 in the last program. Here is the code.

#include <iostream>
#include <vector>

using namespace std;

voidDisplay(vector<int> m)
{
for(int i=0;i<m.capacity();i++)
cout<<m.at(i)<<endl;
}

int main()
{
int array[]={5,6,3,4};
vector<int> nums;
for(int i=1;i<11;i++)
nums.push_back(i);
cout<<”Before inserting the array elements at 2nd place the vector was :”<<endl;
Display(nums);
nums.insert(nums.begin()+1,array,array+3);
cout<<”After inserting the array elements at 2nd place the vector is :”<<endl;
Display(nums);
return 0;
}
 

Here is the output of the code

Before inserting the arry elements at 2nd place the vector was :
1
2
3
4
5
6
7
8
9
10
After inserting the array elements at 2nd place the vector is :
1
5
6
3
2
3
4
5
6
7
8
9
10
 

As you can see first 3 array elements are inserted in between 1 and 2 of the original vector.

The last overloaded version of insert allows you to insert one constant value , M number of times in any place within the original vector. Here is the code for inserting three 99 in between 1 and 2.

#include <iostream>
#include <vector>

using namespace std;

voidDisplay(vector<int> m)
{
for(int i=0;i<m.capacity();i++)
cout<<m.at(i)<<endl;
}
int main()
{
int array[]={5,6,3,4};
vector<int> nums;
for(int i=1;i<5;i++)
nums.push_back(i);
cout<<”Before inserting the constant elements at 2nd place the vector was :”<<endl;
Display(nums);
nums.insert(nums.begin()+1,3,99);
cout<<”After inserting the constant elements at 2nd place the vector is :”<<endl;
Display(nums);
return 0;
}
 

Here is the output

Before inserting the constant elements at 2nd place the vector was :
1
2
3
4
After inserting the constant elements at 2nd place the vector is :
1
99
99
99
2
3
4
 

Applications

Suppose we need to put the co-ordinates of a moving point. We can create a structure that holds two values representing the co-ordinates at any moment and then we can create a vector of these points.

Here is the code.

#include <iostream>
#include <vector>
#include <conio.h>//for getch()

using namespace std;

typedef struct Point
{
float x;
float y;
float z;
}Point;//This will represent a point at any point of time.

int main()
{
//No need to tell the compiler how many elements
vector<Point> MovingPoints;
float x,y,z;
Point TemporaryPoint;
for(int i=0;i<4;i++)
{
cout<<”Enter the co-ordinates of the points :”;
cin>>x>>y>>z;
TemporaryPoint.x=x;
TemporaryPoint.y=y;
TemporaryPoint.z=z;
MovingPoints.push_back(TemporaryPoint);
}
vector<Point>::iterator k = MovingPoints.begin();
cout<<”The moving point’s coordinates were “<<endl;
for(;k<MovingPoints.end();k++)
cout<<”(“<<k->x<<”,”<<k->y<<”,”<<k->z<<”)”<<endl;

getch();
return 0;
}
 

Here is the output of the code

Enter the co-ordinates of the points :1 2 3
Enter the co-ordinates of the points :4 5 6
Enter the co-ordinates of the points :6 7 3
Enter the co-ordinates of the points :4 6 7
The moving point’s coordinates were
(1,2,3)
(4,5,6)
(6,7,3)
(4,6,7)