C++ STL Programming (1)
C++ STL Programming (1)
Lecture
Object Oriented
Programming
STL Programming
Engr. Rashid Farid Chishti
[email protected]
https://youtube.com/rfchishti
https://sites.google.com/site/chishti
Forward Iterators: Supports both reading and writing values in a forward direction.
Bidirectional Iterators: reading and writing values in forward and backward directions.
Random Access Iterator: Supports reading and writing values in any order.
Functors
Function Objects: Callable objects.
Custom operations using operator overloading.
STL Containers
STL Containers
C++ array
set unordered_set
vector stack
multiset unordered_multiset
list queue
map unordered_map
forward_list
priority_queue
multimap unordered_multimap
deque
Sequence Containers
Container Characteristic Advantages and Disadvantages
C++ array Fixed size Quick random access (by index number)
Slow to insert or erase in the middle
Size cannot be changed at runtime
deque Like vector, but can be Quick random access (using index number).
accessed at either end Slow to insert or erase in the middle
Quick insert or erase (push and pop) at either the
beginning or the end
Container Adapters
Container Implementation Characteristic
stack Can be implemented Insert (push) and remove (pop) at one end only
as vector, list, or deque
queue Can be implemented Insert (push) at one end and remove (pop) at other end
as list or deque
priority_queue Can be implemented Insert (push) in random order at one end and remove (pop) in
as vector or deque sorted order from other end
Associative Containers
Container Characteristic Advantages and Disadvantages
set Stores only the key objects The set is used to store unique elements.
Only one key of each value allowed The data is stored in a particular order
(increasing order, by default).
multiset Stores only the key objects Multiset is similar to a set but also
Multiple key values allowed allows duplicate values.
map Associates key object with value object The map contains elements in the form of unique key-
Only one key of each value allowed value pairs. Each key can be associated with only
one value. It establishes a one-to-one mapping. The
key-value pairs are inserted in increasing order of the
keys.
multimap Associates key object with value object Multimap is similar to a map but allows duplicate key-
Multiple key values allowed value pairs to be inserted. Ordering is again done
based on keys.
Some Typical STL Algorithms
Algorithm Purpose
find Returns first element equivalent to a specified value
count Counts the number of elements that have a specified value
equal Compares the contents of two containers and returns true if all corresponding elements are equal
search It commonly used to find a subsequence within a specified range of elements.
copy Copies a sequence of values from one container to another
swap Exchanges a value in one location with a value in another
iter_swap Exchanges a sequence of values in one location with a sequence of values in another location
fill Copies a value into a sequence of locations
sort Sorts the values in a container according to a specified ordering
merge Combines two sorted ranges of elements to make a larger sorted range
accumulate Returns the sum of the elements in a given range
for_each Executes a specified function for each element in the container
Example 1: Using find() Algorithm
#include <iostream>
#include <algorithm> //for find()
using namespace std;
int arr[] = { 11, 22, 33, 44, 55, 66, 77, 88 };
int main(){
int* ptr;
ptr = find(arr, arr+8, 33) ; //find first 33
// The 1st parameter is the iterator of (or in this case the pointer to)
the
// first value to be examined
// The 2nd parameter is the iterator of (or in this case the pointer to)
the
// last value to be examined
cout << "First object with value 33 found at offset " << (ptr-arr) <<
endl;
return 0;
Example 2: Using count() Algorithm
#include <iostream>
#include <algorithm> //for count()
using namespace std;
int arr[] = { 33, 22, 33, 44, 33, 55, 66, 77};
int main() {
int n = count(arr, arr+8, 33) ; //count number of 33’s
cout << "There are " << n << " 33's in arr." << endl;
return 0;
}
Example 3: Using search() Algorithm
#include <iostream>
#include <algorithm>
using namespace std;
int source[] = { 11, 44, 33, 11, 22, 33, 11, 22, 44 };
int pattern[] = { 11, 22, 33 };
int main(){
int* ptr;
ptr = search(source, source+9, pattern, pattern+3) ;
return 0;
}
Example 4: Using sort() Algorithm
#include <iostream>
#include <algorithm>
using namespace std;
//array of numbers
int arr[] = {45, 2, 22, -17, 0, -30, 25, 55};
int main(){
sort(arr, arr+8); // sort the numbers
for(int j=0; j<8; j++) // display sorted array
cout << arr[j] <<' ';
cout << endl;
return 0;
}
Example 5: Using greater<>() Function Object
#include <iostream>
#include <algorithm> // for sort()
#include <functional> // for greater<>
using namespace std;
double fdata[] = { 19.2, 87.4, 33.6, 55.0, 11.5, 42.2 }; // array of doubles
int main(){
sort( fdata, fdata+6, greater<double>() ); // sort the doubles
for(int j=0; j<6; j++) // display sorted doubles
cout << fdata[j] << ' ';
int main(){
sort(names, names+6, alpha_comp); // sort the strings
for(int j=0; j<6; j++) // display sorted strings
cout << names[j] << endl;
return 0;
}
bool alpha_comp(char* s1, char* s2){ // returns true if s1<s2
return ( strcmp(s1, s2)<0 ) ? true : false;
}
Example 7: Using merge() Algorithm
#include <iostream>
#include <algorithm> // for merge()
using namespace std;
int src1[] = { 2, 3, 4, 6, 8 };
int src2[] = { 1, 3, 5 };
int dest[8];
int main(){
return 0;
}
The for_each() Algorithm
The for_each() algorithm allows you to do something to every item in a container.
You write your own function to determine what that “something” is.
Your function can’t change the elements in the container, but it can use or display their
values.
Here’s an example in which for_each() is used to convert all the values of an array from
inches to centimeters and display them.
We write a function called in_to_cm() that multiplies a value by 2.54, and use this function’s
address as the third argument to for_each().
Here’s the listing for FOR_EACH:
Example 9: Using for_each() Algorithm
#include <iostream>
#include <algorithm>
using namespace std;
void in_to_cm(double); // declaration
int main() {
double inches[] = { 3.5, 6.2, 1.0, 12.75, 4.33 }; // array of inches
values
for_each(inches, inches+5, in_to_cm); // output as centimeters
int main(){
double inches[] = { 3.5, 6.2, 1.0, 12.75, 4.33 }; // array of inches
values
double centi[5];
double in_to_cm(double); // prototype
transform(inches, inches+5, centi, in_to_cm); // transform into
array centi[]
for(int j=0; j<5; j++) // display array
centi[]
cout << centi[j] << ' ';
cout << endl;
return 0;
}
insertion or erasure must be moved to make space for the new element or close up the
space where the erased item was.
However, insertion and erasure may nevertheless be useful if speed is not a factor.
You can use vectors much like arrays, accessing elements with the [] operator.
Such random access is very fast with vectors.
It’s also fast to add (or push) a new data item onto the end (the back) of the vector. When
this happens, the vector’s size is automatically increased to hold the new item.
Example 11: Using vector Container
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v ; // create a vector of integers
v.push_back(10) ; // use push_back() to put values at end of array
v.push_back(11);
v.push_back(12);
v.push_back(13);
We add 2 to the begin() member function to specify element 2 (the third element) in the
vector.
The elements from the insertion point to the end of the container are moved upward to
make room, and the size of the container is increased by 1.
The erase() member function removes the element at the specified location.
The elements above the deletion point are moved downward, and the size of the
container is decreased by 1.
List
An STL list container is a doubly linked list, in which each element contains a pointer not
only to the next element but also to the preceding one.
The container stores the address of both the front (first) and the back (last) elements,
which makes for fast access to both ends of the list.
Insertion and deletion of elements are fast and efficient compared to some other data
structures, such as vectors or arrays, especially when dealing with elements in the middle of
the list.
Unlike vectors, lists don't require reallocation when elements are added or removed. This
can be advantageous in scenarios where the number of elements changes frequently.
Unlike arrays or vectors, lists do not support constant-time random access to elements.
Accessing elements by index takes linear time because you have to traverse the list from the
beginning or end to reach the desired position.
Doubly-linked lists have a higher memory overhead compared to vectors because each
element in the list requires storage for the data and two pointers (prev and next).
Example 15: Using list Container
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> ilist;
ilist.push_back(30); // push items on back {30}
ilist.push_back(40); // {30, 40}
ilist.push_front(20); // push items on front {20, 30, 40}
ilist.push_front(10); // {10 ,20, 30, 40}
int size = ilist.size(); // number of items = 4
for(int j=0; j<size; j++){
cout << ilist.front() << ' '; // read item from front
ilist.pop_front(); // pop item off front
}
cout << endl;
return 0;
}
Example 16: Using deque(), merge(), and unique()
#include <iostream>
#include <list>
using namespace std;
int main(){
int j; list<int> list1, list2;
int arr1[] = { 40, 30, 20, 10 }; int arr2[] = { 15, 20, 25, 30, 35 };
for(j=0; j<4; j++) list1.push_back( arr1[j] ); // list1: 40, 30, 20, 10
for(j=0; j<5; j++) list2.push_back( arr2[j] ); // list2: 15, 20, 25, 30,
35
list1.reverse(); // reverse list1: 10 20
30 40
list1.merge(list2); // merge list2 into list1: 10 15 20 20 25 30 30 35 40
list1.unique(); // remove duplicate 20 and 30 list1: 10 15 20 25
30 35 40
int size = list1.size();
while( !list1.empty() ) {
cout << list1.front() << ' '; // read item from front 10 15 20 25 30 35
40
list1.pop_front(); // pop item off front
Deque
A deque is like a vector in some ways and like a linked list in others. Like a vector, it supports
random access using the [] operator.
However, like a list, a deque can be accessed at the front as well as the back. It’s a sort of
double-ended vector, supporting push_front(), pop_front() and front().
Memory is allocated differently for vectors and queues. A vector always occupies a
contiguous region of memory. If a vector grows too large, it may need to be moved to a new
location where it will fit. A deque, on the other hand, can be stored in several non-
contiguous areas; it is segmented.
A member function, capacity(), returns the largest number of elements a vector can
store without being moved, but capacity() isn’t defined for deques because they don’t need
to be moved.
Example 17: Using dque
#include <iostream>
#include <deque>
using namespace std;
int main(){
deque<int> deq;
deq.push_back(30); // push items on back {30}
deq.push_back(40); // {30,40}
deq.push_back(50); // {30,40,50}
deq.push_front(20); // push items on front {20,30,40,50}
deq.push_front(10); // {10,20,30,40,50}
deq[2] = 33; // change middle item {10,20,33,40,50}
return 0;
}
Sequence Containers
Iterators
Iterators are central to the operation of the STL. The role played by iterators is
As a smart pointer
For example, in the following code a pointer ptr iterates through an array and accesses
Here, we dereference the pointer ptr with the * operator to obtain the value of the item it
points to and increment it with the ++ operator so it points to the next item.
Iterators as Smart Pointers
Plain C++ pointers face limitations with more sophisticated containers, particularly when
items are not contiguous in memory.
For non-contiguous structures like linked lists, incrementing a pointer becomes complex as
items are not necessarily adjacent.
Storing a pointer to a container element may lead to issues if the container is modified
(insertions or deletions), affecting the validity of the stored pointer.
Smart pointers provide a solution to these problems by wrapping member functions around
ordinary pointers.
These smart pointers can handle non-contiguous memory or changes in element locations.
Overloading operators like ++ and * allows smart pointers to operate on elements within
their containers, offering a solution to challenges posed by dynamic container
modifications.
Here’s how that might look, in skeleton form:
Iterator categories
If an algorithm needs only to step forward through a container, reading (but not writing to)
one item after another, it can use an input iterator to connect itself to the container.
Actually, input iterators are typically used, not with containers, but when reading from files
or cin.
If an algorithm steps through the container in a forward direction but writes to the
container instead of reading from it, it can use an output iterator. Output iterators are
typically used when writing to files or cout.
If an algorithm steps along forward and may either read from or write to a container, it
must use a forward iterator.
If an algorithm must be able to step both forward and back through a container, it must use
a bidirectional iterator.
if an algorithm must access any item in the container instantly, it must use a random access
iterator. Random access iterators are like arrays, in that you can access any element. They
can be manipulated with arithmetic operations, as in iter2 = iter1 + 7;
Capabilities of Different Iterator Categories
You can see, all iterators support ++ operator for stepping forward through the container.
The input iterator can use the * operator on the right side of the equal sign (but not on
the left): value = *iter;
The output iterator can use the * operator only on the right: *iter = value;
Iterator Types Accepted by Containers
STL automatically makes a bidirectional iterator for list, because that’s what a list requires.
An iterator to a vector or a deque is automatically created as a random-access iterator.
Type of Iterator Required by Algorithms
// display list
for(it = iList.begin(); it != iList.end(); it++)
cout << *it << ' ';
int main(){
int beginRange, endRange;
int arr[] = { 11, 13, 15, 17, 19, 21, 23, 25, 27, 29 };
but unfortunately this doesn’t work. (For one thing, the range will be wrong (from n to 1,
instead of from n–1 to 0).
To iterate backward you can use a reverse iterator. The ITEREV program shows an example
where a reverse iterator is used to display the contents of a list in reverse order.
Common STL Containers: Map
Advantages:
Fast Search Time: The elements in a map are stored in a sorted order based on their
keys. This allows for efficient binary search operations, resulting in fast retrieval of
elements.
Automatic Sorting: The map automatically sorts its elements based on the keys. This can
be a significant advantage when the data needs to be maintained in a sorted order
without the need for manual sorting.
Unique Keys: Each key in a map must be unique. This property ensures that there are no
duplicate keys, which can be useful in scenarios where uniqueness is crucial.
Associative Relationships: The map allows you to establish associations between keys
and values. So, it is easy to implement and work with key-value pairs in your program.
Dynamic Size: The size of a map can change dynamically as elements are inserted or
removed. This dynamic nature is useful when the number of elements in the container is
not known in advance.
Common STL Containers: Map
Disadvantages:
Memory Overhead: The underlying data structure of a map typically involves the use of
pointers and dynamic memory allocation, which can result in a higher memory overhead
compared to some other containers.
Slower Insertion and Deletion: Inserting or deleting elements in a map involves
maintaining the sorted order of the elements, which can make these operations slower
compared to unordered containers like unordered_map.
Not Cache-Friendly: The memory access patterns of a binary search tree, on which a map
is based, may not be as cache-friendly as some other data structures. This can lead to
slightly slower performance in certain scenarios.
Limited Iteration Performance: While searching for a specific element is efficient,
iterating over all elements in a map may not be as fast as in some other containers,
especially when compared to contiguous memory structures.
Common STL Containers: Set
Advantages:
Sorted Order: std::set maintains elements in sorted order, which can be advantageous in
scenarios where you need to iterate through elements in a specific order.
Unique Elements: std::set ensures that all elements are unique. This can simplify certain
algorithms and prevent duplicate entries.
Efficient Lookup: Searching for an element in a set has a time complexity of O(log n),
making it efficient for applications that require quick lookups.
Dynamic Size: std::set dynamically adjusts its size, making it suitable for situations where
the number of elements can change during runtime.
Common STL Containers: Set
Disadvantages:
No Duplicate Elements: While the uniqueness of elements can be an advantage, it can
also be a limitation if you need to store multiple occurrences of the same value.
Insertion Overhead: Inserting elements into a std::set can have a higher time complexity
(O(log n)) compared to other containers like std::unordered_set. If frequent insertions
and removals are necessary, an std::unordered_set might be a better choice.
Limited Operations: std::set does not support direct access or modification of individual
elements. You can only insert and remove elements, but not change them directly. If you
need to modify elements, you may need to erase and insert again.
Memory Overhead: std::set may have a higher memory overhead compared to other
containers due to the need to maintain a sorted order. If memory efficiency is crucial,
you might want to consider alternatives.
Iterating Complexity: Although elements are in sorted order, iterating through them has
a time complexity of O(n), where n is the size of the set. If frequent iteration is a primary
concern, other containers like std::vector may be more suitable.
Common STL Containers: Queue
Advantages:
Efficient for FIFO Operations: Queues are designed to efficiently handle elements in a
first-in, first-out manner.
Easy to Use: It provides a simple and easy-to-use interface, with member functions like
push(), pop(), front(), and empty().
Memory Management: STL queues handle memory management internally, so
developers don't need to worry about memory allocation and deallocation when adding
or removing elements.
Thread Safety: In a multithreaded environment, STL queues (like other containers in STL)
are generally thread-safe, meaning they can be safely used in concurrent programs
without the need for additional synchronization.
Standardized Interface: Being part of the C++ Standard Template Library, queues in STL
provide a standardized interface. This allows for consistency and portability across
different C++ implementations.
Common STL Containers: Queue
Disadvantages:
Fixed Size: STL queues have a fixed size once created. If dynamic resizing is required,
developers may need to implement their own logic or choose a different container type.
No Random Access: Unlike some other data structures, queues do not provide random
access to elements. Access is limited to the front and back of the queue, which might be
a limitation in certain scenarios.
Limited Functionality: While queues are excellent for FIFO operations, they may not be
the best choice for scenarios that require operations like sorting or searching. Other data
structures, such as vectors or lists, might be more suitable for such cases.
Overhead: There might be a slight overhead associated with using a queue due to the
additional functionality it provides. For simple scenarios, this overhead may be
unnecessary.
Dependency on STL: If you're working in an environment where STL is not available or
not desired, using STL containers, including queues, might not be an option.
Common STL Containers: Stack
Stack
Characteristics:
LIFO (Last-In, First-Out) data structure
Typically implemented using deque or vector.
Use Cases:
When you need to implement a stack or perform operations like depth-first search.
STL Iterators
What are Iterators?
Iterators are objects that allow you to traverse the elements of a container.
Iterators act as pointers to elements within a container.
Types of Iterators
begin(): Points to the first element.
end(): Points one past the last element.
different containers may have additional types of iterators (e.g., rbegin(), rend() for reverse iteration).
How to Use Iterators
Initialize an iterator using begin().
Iterate through the container elements using a loop.
Terminate the loop when the iterator reaches end().
Code Example: Iterating through a Vector
Next code demonstrates how to use iterators to iterate through a vector
Benefits of Iterators
Advantages of using iterators are abstraction, compatibility with various containers, and ease of use.
Example 1: Using Iterator
#include <iostream>
#include <vector>
int main() {
std::vector<int> v1 = { 1, 2, 3, 4, 5 };
// Using iterators to traverse the vector
for (std::vector<int>::iterator it = v1.begin(); it != v1.end(); ++it) {
std::cout << *it << " ";
}
return 0;
}
Page
Using Iterators to Manipulate Containers
Iterators provide a way to access and manipulate each element of the container
sequentially, allowing you to update, insert, or delete elements as needed.
This approach is often used for tasks like searching and replacing specific elements, filtering
data, or applying transformations to elements within the container without the need for
explicit loops.
It can make code more efficient and concise by abstracting away the iteration logic and
providing a clean way to interact with container elements.
Examples of common modifications:
Inserting elements.
Deleting elements.
Modifying elements.
Example 2: Modifying Elements in a List
#include <iostream>
#include <list>
int main() {
std::list<int> myList = { 1, 2, 3, 4, 5 };
return 0;
} Page
Applying Algorithms to Container Data
How to apply algorithms to different container types (e.g., vector, list)
Using iterators to specify the range
Be mindful of container requirements (e.g., random access for sorting)
Algorithm Complexity
Discuss time and space complexity of algorithms
Help choose the right algorithm for specific tasks
Example: O(N log N) for std::sort